web-dev-qa-db-ja.com

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

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

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

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

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

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

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

8
Erik Sapir

クラス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は、BとCの両方の定義を知る必要があります(Cだけではなく)。また、ファクトリメソッドの定義のステップをスキップしたため、プロジェクトに課せられたA、B、C、Dを使用するanyクライアントコードにも制限があります(R1)。

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

6
utnapistim

C++のDIフレームワークがあります(まだAFAIKの開発中です): Boost.DI

Redditのフレームワークについて 便利なコメント があります。

3

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

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

あなたの例を使用すると、クラスAはDを作成する方法を理解するのに十分な「知識」を持っていないようなので、 コントロールを反転 し、これをクラスAに必要な依存関係として公開します。 a factory クラスDのインスタンスを作成する方法を知っています。

0
gnat