まず、この回答のために私が立てた仮定を説明したいと思います。それは常に正しいとは限りませんが、かなり頻繁に:
インターフェイスは形容詞です。クラスは名詞です。
(実は名詞のインターフェースもあるのですが、ここでは一般化したいと思います。)
したがって、たとえば、インターフェースはIDisposable
、 、IEnumerable
などIPrintable
です。クラスは、これらのインターフェースの 1 つ以上の実際の実装です。List
またはMap
、両方が の実装である場合もありますIEnumerable
。
ポイントを理解するには: 多くの場合、クラスは互いに依存しています。たとえば、データベースにアクセスするクラスを作成することもできますがDatabase
(ハァッ、驚き! ;-))、このクラスでデータベースへのアクセスに関するログも記録する必要があります。別のクラスLogger
がDatabase
あり、依存関係があるとしLogger
ます。
ここまでは順調ですね。
Database
次の行を使用して、クラス内でこの依存関係をモデル化できます。
var logger = new Logger();
そしてすべてが順調です。大量のロガーが必要であることに気付くまでは問題ありません。コンソールにログを記録したい場合もあれば、ファイル システムにログを記録したい場合もあれば、TCP/IP とリモート ロギング サーバーを使用したい場合もあります...
そしてもちろん、すべてのコードを変更したくはありません(その間、膨大な数のコードがあります)。すべての行を置き換えます。
var logger = new Logger();
に:
var logger = new TcpLogger();
まず、これは面白くありません。第二に、これはエラーが発生しやすいです。第三に、これは訓練されたサルにとって愚かで反復的な作業です。それで、あなたは何をしますか?
明らかにICanLog
、さまざまなすべてのロガーによって実装されるインターフェイス (または類似のもの) を導入することは非常に良い考えです。したがって、コードのステップ 1 は、次のようにすることです。
ICanLog logger = new Logger();
型推論によって型が変更されることはなくなりました。開発対象のインターフェイスは常に 1 つだけです。次のステップは、何度もやりたくないということですnew Logger()
。したがって、新しいインスタンスを作成するための信頼性を単一の中央ファクトリ クラスに配置すると、次のようなコードが得られます。
ICanLog logger = LoggerFactory.Create();
ファクトリ自体が、作成するロガーの種類を決定します。コードはもはや気にしません。使用されているロガーのタイプを変更したい場合は、一度変更します: ファクトリ内。
もちろん、このファクトリを一般化して、どのタイプでも機能させることができます。
ICanLog logger = TypeFactory.Create<ICanLog>();
この TypeFactory のどこかで、特定のインターフェイス タイプが要求されたときにインスタンス化する実際のクラスの構成データが必要になるため、マッピングが必要です。もちろん、このマッピングはコード内で行うことができますが、型の変更は再コンパイルを意味します。ただし、このマッピングを XML ファイル内に配置することもできます。これにより、コンパイル時 (!) の後でも実際に使用されるクラスを変更できます。つまり、再コンパイルせずに動的に変更できます。
これについて役立つ例を挙げると、正常にログに記録しないソフトウェアを考えてみてください。顧客が問題を抱えているために電話して助けを求めた場合、送信するのは更新された XML 構成ファイルだけです。ログが有効になっており、サポートはログ ファイルを使用して顧客を支援できます。
そして今、名前を少し置き換えると、Service Locatorの単純な実装になります。これは、制御の反転の 2 つのパターンのうちの 1 つです(インスタンス化する正確なクラスを誰が決定するかについて制御を反転するため)。
全体として、これによりコードの依存関係が減少しますが、すべてのコードが中央の単一のサービス ロケーターに依存するようになります。
依存性注入は、この行の次のステップです: サービスロケーターへのこの単一の依存性を取り除くだけです: さまざまなクラスがサービスロケーターに特定のインターフェースの実装を要求する代わりに、もう一度、誰が何をインスタンス化するかの制御を元に戻します.
依存性注入により、Database
クラスには type のパラメーターを必要とするコンストラクターが含まれるようになりましたICanLog
。
public Database(ICanLog logger) { ... }
データベースには常に使用するロガーがありますが、このロガーがどこから来たのかはわかりません。
ここで DI フレームワークの出番です。マッピングをもう一度構成してから、DI フレームワークにアプリケーションのインスタンス化を依頼します。Application
クラスにはICanPersistData
実装が必要なため、 のインスタンスが注入されますが、そのDatabase
ためには、 用に構成された種類のロガーのインスタンスを最初に作成する必要がありますICanLog
。等々 ...
つまり、簡単に言うと、依存関係の挿入は、コード内の依存関係を削除する 2 つの方法のうちの 1 つです。コンパイル後の構成変更に非常に役立ち、単体テストに最適です (スタブやモックの挿入が非常に簡単になるため)。
実際には、サービス ロケーターなしではできないことがあります (たとえば、特定のインターフェイスに必要なインスタンスの数が事前にわからない場合: DI フレームワークは常にパラメーターごとに 1 つのインスタンスのみを挿入しますが、呼び出すことができますもちろん、ループ内のサービスロケーター)、したがって、ほとんどの場合、各 DI フレームワークもサービスロケーターを提供します。
しかし、基本的にはそれだけです。
PS: ここで説明したのは、コンストラクター インジェクションと呼ばれる手法です。コンストラクター パラメーターではなく、依存関係の定義と解決にプロパティが使用されるプロパティ インジェクションもあります。プロパティ注入はオプションの依存関係、コンストラクター注入は必須の依存関係と考えてください。しかし、これに関する議論はこの質問の範囲を超えています。