なぜ依存性注入を使用するのでしょうか? 質問する

なぜ依存性注入を使用するのでしょうか? 質問する

理解しようとしている依存性注入(DI)、またしても失敗しました。馬鹿げているように思えます。私のコードは決して乱雑ではありません。仮想関数やインターフェイスを書くことはほとんどありません (たまに書くことはありますが)。すべての構成は、json.net を使用して (場合によっては XML シリアライザーを使用して) クラスに魔法のようにシリアル化されます。

それがどのような問題を解決するのか、よくわかりません。これは、「こんにちは。この関数を実行すると、このタイプのオブジェクトを返し、これらのパラメータ/データを使用します。」と言う方法のように見えます
が...なぜそれを使用するのでしょうか? 注意してください。私も使用する必要はありませんでしたobjectが、それが何のためにあるのかは理解しています。

ウェブサイトやデスクトップ アプリケーションの構築で DI を使用する実際の状況にはどのようなものがありますか? ゲームでインターフェイス/仮想関数を使用する理由は簡単に思いつきますが、ゲーム以外のコードでそれを使用することは非常にまれです (1 つの例も思い出せないほどまれです)。

ベストアンサー1

まず、この回答の前提となる仮定を説明したいと思います。これは常に当てはまるわけではありませんが、多くの場合当てはまります。

インターフェースは形容詞であり、クラスは名詞です。

(実は名詞であるインターフェースもあるのですが、ここでは一般化したいと思います。)

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();

ファクトリ自体が、作成するロガーの種類を決定します。コードではこれ以上気にする必要はなく、使用するロガーの種類を変更したい場合は、ファクトリ内で1 回変更するだけです。

もちろん、このファクトリを一般化して、任意の型で動作するようにすることができます。

ICanLog logger = TypeFactory.Create<ICanLog>();

この TypeFactory は、特定のインターフェース タイプが要求されたときに実際にインスタンス化するクラスに関する構成データをどこかで必要とするため、マッピングが必要です。もちろん、このマッピングをコード内で実行できますが、その場合、タイプを変更すると再コンパイルが必要になります。ただし、このマッピングを XML ファイル内に配置することも可能で、たとえば、こうすると、コンパイル後でも実際に使用されるクラスを変更できます (!)。つまり、再コンパイルせずに動的に変更できるのです。

これについて役立つ例を挙げてみましょう。通常はログを記録しないソフトウェアを考えてみましょう。しかし、顧客が問題を抱えて電話をかけてきて助けを求めてきたとき、更新された XML 構成ファイルだけを顧客に送信すると、顧客のログ記録が有効になり、サポート担当者はログ ファイルを使用して顧客を支援できるようになります。

ここで、名前を少し置き換えると、Service Locatorの単純な実装になります。これは、制御の反転の 2 つのパターンのうちの 1 つです(インスタンス化するクラスを正確に誰が決定するかの制御を反転するため)。

全体として、これによりコード内の依存関係が削減されますが、すべてのコードが中央の単一のサービス ロケータに依存するようになります。

依存性の注入は、このラインの次のステップです。サービス ロケータへのこの単一の依存性を取り除くだけです。さまざまなクラスがサービス ロケータに特定のインターフェイスの実装を要求する代わりに、もう一度、誰が何をインスタンス化するかの制御を元に戻します。

依存性注入により、Databaseクラスには 型のパラメータを必要とするコンストラクターが含まれるようになりましたICanLog

public Database(ICanLog logger) { ... }

これで、データベースには常に使用するロガーが存在するようになりましたが、このロガーがどこから来るのかはデータベースにはわかりません。

ここで DI フレームワークが役立ちます。マッピングをもう一度構成し、DI フレームワークにアプリケーションのインスタンスを作成するように依頼します。Applicationクラスには実装が必要なのでICanPersistData、 のインスタンスDatabaseが注入されますが、そのためには、 用に構成されている種類のロガーのインスタンスを最初に作成する必要がありますICanLog。などなど...

つまり、長い話を短くすると、依存性注入は、コード内の依存性を削除する 2 つの方法のうちの 1 つです。これは、コンパイル後の構成変更に非常に役立ち、ユニット テストにも最適です (スタブやモックの注入が非常に簡単になるので)。

実際には、サービス ロケータなしではできないことがあります (たとえば、特定のインターフェイスのインスタンスがいくつ必要か事前にわからない場合: DI フレームワークは常にパラメーターごとに 1 つのインスタンスのみを挿入しますが、ループ内でサービス ロケータを呼び出すことはできます)。そのため、ほとんどの場合、各 DI フレームワークはサービス ロケータも提供します。

しかし、基本的にはそれだけです。

PS: ここで説明したのはコンストラクター インジェクションと呼ばれる手法ですが、コンストラクター パラメーターではなくプロパティを使用して依存関係を定義および解決するプロパティ インジェクションもあります。プロパティ インジェクションはオプションの依存関係、コンストラクター インジェクションは必須の依存関係と考えてください。ただし、これに関する議論はこの質問の範囲を超えています。

おすすめ記事