注目キーワード
  1. プログラミング
  2. C#

【C#】依存性注入 (DI:Dependency Injection) について

C#

はじめに

こうちゃ
こんにちは、こうちゃです。

C#でクラス設計をしていると、依存性注入 (DI : Dependency Injection) というものがとても重要になってきます。

以下の記事で説明したポリモーフィズムが依存性注入に深く関係しています。

関連記事

はじめに こうちゃ こんにちは、こうちゃです。 オブジェクト指向の重要な概念として、ポリモーフィズム (多態性) というものがあります。 これまでみてきた abstract virtual […]

今回は、依存性注入 (DI) の仕組みとメリットについて 説明していきます。

  • 依存性注入 (DI) とはなにか
  • 依存性注入 (DI) のメリット
  • クラス単位のテストを行うなど保守性について関心のある方
  • オブジェクト指向に興味のある方
こうちゃ
それでは、やっていきましょう!

依存性注入 (DI) とは

依存性注入とは、必要な依存関係 (インスタンスなど) を外部から渡す仕組み です。
実際に例を見ていきましょう。

DIなしの例

class Game
{
    private Enemy enemy;

    public Game()
    {
        enemy = new Enemy();
    }

    public void Start()
    {
        enemy.Attack();
    }
}

上記のようなクラス設計の場合、何が問題になるでしょうか。

それは、メンバ変数「enemy」が他クラスに差し替えられない ということです。

もちろん、この「Game」クラス自体を修正すれば差し替えられますが、非常に柔軟性・拡張性が低いです。
現在のこの設計は「Game」クラスが「Enemy」クラスに強く依存している と言えます。

DIを利用しない際の問題点

  • 特定のクラス (今回だと「Enemy」) に強く依存する
  • 別クラスに差し替えができない
  • テストなどが実施しにくい (後述)

DIありの例

interface IEnemy
{
    void Attack();
}
class Enemy : IEnemy
{
    public void Attack()
    {
        Console.WriteLine("攻撃!");
    }
}
class Game
{
    private IEnemy enemy;

    public Game(IEnemy enemy)
    {
        this.enemy = enemy;
    }

    public void Start()
    {
        enemy.Attack();
    }
}

DIなしの時と「Game」クラスを比較して、

  • メンバのインスタンスが「Enemy」(クラス) から「IEnemy」(インターフェース) に変更
  • コンストラクタで「IEnemy」のインスタンスを引数で渡し、それをメンバのインスタンスに注入
    (DIなしでは「Enemy」クラスでインスタンス化)

という違いがあります。

こうすることで、インスタンス「enemy」は外部で作成して「Game」クラス生成時に注入することが可能になります。

IEnemy enemy = new Enemy();
Game game = new Game(enemy);
game.Start();

DI利用のポイント

  • 「Game」は「enemy」の実体を知らない
  • 「IEnemy」にのみ依存しており、「enemy」を差し替え可能

→DIを利用すると 依存度を弱め、柔軟性・拡張性が上がる

依存性注入 (DI) のメリット

インスタンス実体の差し替えが簡単

メリット1つ目として、差し替えが簡単になる ということがあります。

class Boss : IEnemy
{
    public void Attack()
    {
        Console.WriteLine("強力攻撃!");
    }
}
IEnemy boss = new Boss();
Game game = new Game(boss);
game.Start();

このように、「Game」クラスの「enemy」インスタンスに「Boss」クラスのインスタンスを渡すことも可能です。
「Game」クラスは一切変更せずに、中身の動き (「enemy」に関わる部分) を変えることができることはメリットと言えるでしょう。

テストが実施しやすい

メリット2つ目としては、「Game」クラスのテストがやりやすくなることです。

class MockEnemy : IEnemy
{
    public int attackCallCount = 0;
    public void Attack()
    {
        attackCallCount++;
    }
}
IEnemy mockEnemy = new MockEnemy();
Game game = new Game(mockEnemy);
game.Start();
Console.WriteLine($"Attackメソッドの呼び出し回数: {((MockEnemy)mockEnemy).attackCallCount}");

「Attack」メソッドでメンバ変数をインクリメントする「MockEnemy」クラスを作成しました。
「MockEnemy」を「Game」クラスに渡すことで、「game.Start()」で「enemy.Attack()」が正常に呼ばれているかを確認できるようになります。

このように、「Game」クラスのテストの際に「enemy」にテスト用クラスを注入できるため、テストが簡単に実施できるのは大きなメリットと言えます。

クラス同士が疎結合になる

DIなしの例のように、「Game」クラスが「Enemy」クラスのインスタンスを所持している状態は、クラスが密結合であると言えます。

ですがDIを利用することで、「Game」クラスが「Enemy」クラスに依存しなくなったので、疎結合になりました。

このように、クラス同士の結びつきが弱くなると変更に強い設計となるため、より保守性が向上するというメリットがあります。

ポリモーフィズムと依存性注入 (DI) の関係

冒頭で、ポリモーフィズムはDIに深く関係していると言いました。

ポリモーフィズムは 同じ呼び出し方で動作が異なる仕組み でした。

DIはこのポリモーフィズムという仕組みがあるからこそ成り立っていると言えます。
ポリモーフィズムがあるから 異なる動作をする実体を外部から渡す ことができます。

今回の例では、ポリモーフィズムをインターフェースを使って実現しました。
この3つの関係は以下のようになります。

  • インターフェース → 呼び方を統一
  • ポリモーフィズム → 動作を変える
  • DI → 動作を変えるための実体を差し替える

このように、DIは3つセットで初めて機能する仕組みになります。

まとめ

依存性注入 (DI) は、依存するインスタンスを外部から渡す仕組み になります。

以下のように考えましょう。

  • DIなし → 単一のクラスしか使えない
  • DIあり → 宣言されているインターフェースを継承している全クラスで差し替え可能

さいごに

今回は、依存性注入 (DI) について説明しました。

インターフェースとかポリモーフィズムとか、今まで取り上げてきた内容が登場しましたね。
DIについては、ポリモーフィズムという仕組みがあるからこそ成り立っている機能です。
このように、各機能・仕組みの関係がわかってくると、「なんでこの機能・仕組みが必要なの?」がわかってきておもしろいですね。

DIについては、今後の記事でも登場予定ですので、次の登場を楽しみにしていてください。(笑)
今回はここまで♪

こうちゃ
それではみなさま、お疲れ様でした!
楽しいプログラミングライフを!
最新情報をチェックしよう!