1. ホーム
  2. dependency-injection

[解決済み] なぜ依存性注入を使用するのですか?

2022-03-17 11:11:34

質問

を理解しようとしています。 依存性注入 (DI)ですが、またしても失敗しました。バカバカしいとしか言いようがない。仮想関数やインターフェースはほとんど書いていないし(たまに書くけど)、設定はすべてjson.netを使って魔法のようにクラスにシリアライズされている(XMLシリアライザを使うこともある)。

どのような問題を解決するのか、よく理解できないのですが。これは、次のように言っているように見えます: "こんにちは。この関数に遭遇したら、このタイプでこれらのパラメータ/データを使用するオブジェクトを返してください。
でも...なんでそんなの使うんだろう?私はこれまで object もそうですが、何のためにあるのかがよくわかります。

Webサイトやデスクトップアプリケーションの構築において、実際にDIを使用する場面はどのようなものでしょうか。ゲームでインターフェースや仮想関数を使いたい理由は簡単に思いつきますが、ゲーム以外のコードでそれを使うのは非常に稀です(一例も思い出せないくらい稀です)。

どのように解決するのか?

まず、この答えを出すための前提を説明したいと思います。これは常に正しいというわけではありませんが、かなりの頻度で行われます。

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

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

ですから、例えば、インターフェースは次のようなものです。 IDisposable , IEnumerable または IPrintable . クラスは、これらのインターフェースの1つまたは複数の実際の実装です。 List または Map の実装である可能性があります。 IEnumerable .

ポイントを押さえるために 多くの場合、クラスはお互いに依存し合っています。例えば Database クラスはデータベースにアクセスしますが、このクラスはデータベースへのアクセスに関するロギングも行いたいとします。別のクラス Logger では Database には依存関係があります。 Logger .

ここまではいいんです。

この依存関係をモデル化するには Database クラスは以下の行で構成されています。

var logger = new Logger();

で、すべて順調です。たくさんのロガーが必要だと気づくまではよかったのですが。あるときはコンソールに、あるときはファイルシステムに、あるときはTCP/IPとリモートロギングサーバを使ってログを取りたい、などなど......。

そしてもちろん、あなたは NOT は、すべてのコードを変更し、すべての行を置き換える必要があります。

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設定ファイルだけで、今彼はログを有効にし、あなたのサポートは顧客を助けるためにログファイルを使うことができます。

そして今、名前を少し置き換えると、結局はシンプルな実装の サービスロケーター の2つのパターンのうちの1つです。 制御の逆転 (インスタンス化する正確なクラスを誰が決定するかの制御を逆転させるため)。

全体として、これはあなたのコードの依存性を減らしますが、今、あなたのすべてのコードは、中央の単一のサービスロケーターへの依存性を持っています。

依存性注入 が、この行の次のステップになります。サービスロケータへのこの単一の依存を取り除くだけです。様々なクラスがサービスロケータに特定のインターフェイスの実装を問い合わせる代わりに、誰が何をインスタンス化するかという制御を再び元に戻すのです。

依存性注入を使用すると Database 型のパラメータを必要とするコンストラクタを持つようになりました。 ICanLog :

public Database(ICanLog logger) { ... }

これで、データベースは常に使用するロガーを持ちますが、このロガーがどこから来たのかはもうわかりません。

そして、ここでDIフレームワークの出番です。もう一度マッピングを設定し、DIフレームワークにアプリケーションのインスタンス化を依頼します。このように Application クラスは ICanPersistData のインスタンスを実装しています。 Database に設定されているロガーのインスタンスを作成する必要があります。 ICanLog . といった具合です.

つまり、長い話を短くすると、依存性注入は、コード内の依存性を除去する方法の2つのうちの1つです。コンパイル後の設定変更にとても便利で、ユニットテストにも最適です(スタブやモックのインジェクションがとても簡単になるからです)。

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

でも、基本的にはこれで終わりです。

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