最初に、私がこの回答に対して行う前提について説明します。それは常に正しいとは限りませんが、かなり頻繁に:
インターフェイスは形容詞です。クラスは名詞です。
(実際には、名詞でもあるインターフェースがありますが、ここで一般化したいと思います。)
だから、インタフェースを例えばすることなど何もあり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の単純な実装になります。これは、Inversion of Controlの 2つのパターンの1つです(インスタンス化する正確なクラスをだれが決定するかについて制御を反転するため)。
これにより、コード内の依存関係が減少しますが、すべてのコードが中央の単一のサービスロケータに依存します。
依存性注入がこの行の次のステップになりました:サービスロケーターへのこの単一の依存関係を取り除くだけです:さまざまなクラスが特定のインターフェイスの実装をサービスロケーターに要求する代わりに、もう一度-誰が何をインスタンス化するかの制御を元に戻します。
依存性注入により、Database
クラスに型のパラメーターを必要とするコンストラクターが追加されましたICanLog
。
public Database(ICanLog logger) { ... }
これで、データベースには常に使用するロガーがありますが、このロガーがどこから来たかはわかりません。
これがDIフレームワークの出番です。もう一度マッピングを構成してから、DIフレームワークにアプリケーションのインスタンスを作成するように依頼します。Application
クラスに必要なICanPersistData
実装のインスタンスがDatabase
注入される-しかし、そのためには、最初のために構成されているロガーの種類のインスタンスを作成する必要がありますICanLog
。等々 ...
したがって、長い話を簡単に言うと、依存関係の注入は、コード内の依存関係を削除する2つの方法のうちの1つです。これは、コンパイル後の構成変更に非常に役立ち、単体テストに最適です(スタブやモックの挿入が非常に簡単になるため)。
実際には、サービスロケーターなしでは実行できないことがいくつかあります(たとえば、特定のインターフェイスに必要なインスタンスの数が事前にわからない場合):DIフレームワークは常にパラメーターごとに1つのインスタンスのみを注入しますが、もちろん、ループ内のサービスロケーター)、したがって、ほとんどの場合、各DIフレームワークもサービスロケーターを提供します。
しかし、基本的にはそれだけです。
PS:ここで説明したのは、コンストラクターインジェクションと呼ばれる手法です。コンストラクターパラメーターではなくプロパティインジェクションもありますが、プロパティは依存関係の定義と解決に使用されます。プロパティインジェクションはオプションの依存関係、コンストラクタインジェクションは必須の依存関係と考えてください。しかし、これに関する議論はこの質問の範囲を超えています。