C ++アプリケーションでの依存関係(DI)の注入


8

依存性注入で遊んでいますが、正しく実行されているかどうかはわかりません。特に、依存関係が注入されたクラスを構築するための正しい方法が何かはわかりません。

クラスBを作成するクラスAがあるとします。クラスBはクラスCに依存し、クラスCはクラスDに依存します。クラスDの作成を担当するのは誰ですか?

  1. クラスAの場合もあります。ただし、大規模なシステムでは、クラスAが非常に多数のオブジェクトを作成してアセンブルする場合があります。

  2. D、C、Bを作成する個別のビルダークラス。Aはこのビルダークラスを使用します。

  3. その他のオプション。

また、DIコンテナーについてもよく読みました。ただし、C ++には主要なフレームワークがないようです。また、私が正しく理解すれば、コンテナーがなくてもDIはうまく実行できます。私は正しいですか?





...もちろん、実際にはDIコンテナが必要だと仮定します。ほとんどのアプリケーションはそうではありません。
ロバートハーベイ

コンテナーが必要かどうかはどうすればわかりますか?
Erik Sapir 2014年

回答:


6

クラスBを作成するクラスAがあるとします。クラスBはクラスCに依存し、クラスCはクラスDに依存します。クラスDの作成を担当するのは誰ですか?

あなたはステップをジャンプしています。疎結合と例外安全のために最適化された一連の規則を検討してください。ルールは次のようになります。

  • R1: AにBが含まれている場合、Aのコンストラクターは完全に構築されたBを受け取ります(つまり、「Bの構築依存関係」ではありません)。同様に、Bの構築にCが必要な場合、Cの依存関係ではなくCを受け取ります。

  • R2:オブジェクトを構築するためにオブジェクトの完全なチェーンが必要な場合、チェーンされた構造はファクトリ(関数またはクラス)内で抽出/自動化されます。

コード(std :: move呼び出しは簡略化のために省略されています):

struct D { int dummy; };
struct C { D d; };
struct B { C c; }

struct A { B make_b(C c) {return B{c}; };

そのようなシステムでは、「だれがDを作成するか」は無関係です。なぜなら、make_bを呼び出すときは、DではなくCが必要だからです。

クライアントコード:

A a; // factory instance

// construct a B instance:
D d;
C c {d};
B = a.make_b(c);

ここで、Dはクライアントコードによって作成されます。当然、このコードが複数回繰り返されている場合は、自由に関数に抽出できます(上記のR2を参照)。

B make_b_from_d(D& d) // you should probably inject A instance here as well
{
    C c {d};
    A a;
    return a.make_b(c);
}

make_bの定義をスキップし(R1を無視)、次のようにコードを直接作成する自然な傾向があります。

struct D { int dummy; };
struct C { D d; };
struct B { C c; }

struct A { B make_b(D d) { C c; return B{c}; }; // make B from D directly

この場合、次の問題があります。

  • モノリシックコードがあります。クライアントコードで、既存のCからBを作成する必要がある場合は、make_bを使用できません。新しいファクトリを作成するか、make_bの定義と、古いmake_bを使用するすべてのクライアントコードを作成する必要があります。

  • ソースを見ると、依存関係の見方が混乱しています。ソースを見ると、実際にはCだけが必要な場合でも、Dインスタンスが必要であると考えるようになります。

例:

void sub_optimal_solution(C& existent_c) {
    // you cannot create a B here using existent_C, because your A::make_b
    // takes a D parameter; B's construction doesn't actually need a D
    // but you cannot see that at all if you just have:
    // struct A { B make_b(D d); };
}
  • を省略するとstruct A { B make_b(C c); }結合が大幅に増加します。Aは(Cだけでなく)BとCの両方の定義を知る必要があります。また、ファクトリメソッド(R1)の定義のステップをスキップしたため、プロジェクトに課されたA、B、C、Dを使用するクライアントコードに制限があります。

TLDR:つまり、最も外側の依存関係をファクトリに渡さず、最も近い依存関係をファクトリに渡します。これにより、コードが堅牢かつ簡単に変更可能になり、提起された質問(「Dを作成した人」)がmake_bの実装に関する無関係な質問にレンダリングされます(make_bはDを受信しなくなったため、より直接的な依存関係-C-となるため、これはmake_b)のパラメーターとして挿入されます。


1
Dを作成する必要があるという問題はまだ残っています。私はまだ、コンストラクトDに責任があるシステムのどの部分を理解しようとしています
エリック・サピア


0

クラスDの作成を担当するのは誰ですか?

このような質問がポップアップするたびに、それは注入する依存関係を示しています。全体のアイデアは、オブジェクトの外部で責任を「委任する」ことであり、オブジェクトの処理方法を理解するのに問題があるように見えます。

クラスAは、あなたが、Dを作成する方法をフィギュアに十分な「知識」を持っていないようだから、あなたの例を使用して制御反転し、それが必要とすることによって、クラスAのために必要な依存関係として、これを公開する工場作成する方法を知っていますクラスDのインスタンス。


それは理にかなっていますが、最後に、依存関係を取得できないシステム(メイン)の最初のクラスに到達します。このクラスは一連のファクトリー/オブジェクトを初期化する必要があるようです
Erik Sapir

クラスDの作成の責任を処理する方法についての質問は解決されたように見えませんか?システムの最初のクラス(メイン)でどのように「ワイヤリング」するかについては、別の質問になります。個別に投稿することを検討してください。
gnat 2014年

実際、それはまったく別の問題ではありません-問題は、すべての初期化(新しい操作)を実行する責任があるのは誰かに関するものでした。別の質問に値するかどうかはわかりません。いずれにせよ、私はここでこの質問への答えを本当に大好きです
Erik Sapir


1
「ソフトウェアフレームワーク、コールバック、スケジューラー、イベントループ、および依存性注入、制御原理の逆転に従う設計パターンの例です...」(Wikipedia
gnat
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.