web-dev-qa-db-ja.com

依存性注入;ボイラープレートコードを削減するための推奨事項

簡単な質問があります。答えがあるかどうかさえわかりませんが、試してみましょう。私はC++でコーディングしていて、依存性注入を使用してグローバル状態を回避しています。これは非常にうまく機能し、予期しない/未定義の動作を頻繁に実行しません。

しかし、私のプロジェクトが成長するにつれ、ボイラープレートと見なす多くのコードを書いていることに気づきました。さらに悪いことに、実際のコードよりも多くの定型コードがあるため、理解しづらい場合があります。

良い例に勝るものはないので、行こう:

Timeオブジェクトを作成するTimeFactoryというクラスがあります。

詳細(関連があるかどうかは不明):時間オブジェクトは非常に複雑です。これは、時間の形式が異なり、それらの間の変換が直線的でも簡単でもないためです。各「時間」には、変換を処理するシンクロナイザが含まれています。それらが同じ、適切に初期化された、シンクロナイザを持っていることを確認するために、TimeFactoryを使用します。TimeFactoryはインスタンスが1つだけであり、アプリケーション全体に適用されるため、シングルトンの対象になりますが、変更可能であるため、シングルトン

私のアプリでは、多くのクラスがTimeオブジェクトを作成する必要があります。これらのクラスは深くネストされている場合があります。

クラスBのインスタンスを含むクラスAがあるとします。クラスDまで同様です。クラスDはTimeオブジェクトを作成する必要があります。

私の素朴な実装では、TimeFactoryをクラスAのコンストラクターに渡し、それがクラスBのコンストラクターに渡して、クラスDまで続きます。

ここで、TimeFactoryのようなクラスがいくつかあり、上記のようなクラス階層がいくつかあるとします。依存性注入を使用すると想定される柔軟性と可読性がすべて失われます。

アプリに大きな設計上の欠陥はないのかと思い始めています...それとも、依存性注入を使用するのに必要な悪ですか?

どう思いますか ?

25
Dinaiz

私のアプリでは、多くのクラスがTimeオブジェクトを作成する必要があります

Timeクラスは、アプリケーションの「一般的なインフラストラクチャ」に属する非常に基本的なデータ型のようです。そのようなクラスではDIはうまく機能しません。文字列を使用するコードのすべての部分にstringのようなクラスを挿入する必要があり、新しい文字列を作成する唯一の可能性としてstringFactoryを使用する必要がある場合の意味を考えてください。 -プログラムの可読性が桁違いに低下します。

だから私の提案:Timeのような一般的なデータ型にはDIを使用しないでください。 Timeクラス自体の単体テストを作成し、完了したら、stringクラス、vectorクラス、またはその他のクラスのように、プログラムのどこでも使用します標準ライブラリの。実際に互いに分離する必要があるコンポーネントにはDIを使用します。

23
Doc Brown

「依存性注入を使用すると想定される柔軟性と可読性をすべて失う」とはどういう意味ですか-DIは可読性ではありません。オブジェクト間の依存関係を分離することです。

クラスAがクラスB、クラスBがクラスC、クラスCがクラスDを作成しているようです。

クラスAにクラスBを注入する必要があります。クラスCをクラスBに注入します。クラスDをクラスCに注入します。

4
C0deAttack

なぜタイムファクトリをシングルトンにしたくないのかわかりません。アプリ全体でインスタンスが1つしかない場合は、事実上シングルトンです。

とはいえ、同期可能なブロックによって適切に保護されている場合を除いて、可変オブジェクトを共有することは非常に危険です。その場合、シングルトンにならない理由はありません。

依存関係の注入を実行する場合は、アノテーションを使用してパラメーターを自動割り当てできるようにするSpringまたは他の依存関係の注入フレームワークを確認することができます。

3
molyss

これは、Doc Brownに対する補足的な回答となること、およびまだ質問に関連しているDinaizの未回答のコメントに対応することを目的としています。

おそらく必要なのは、DIを実行するためのフレームワークです。複雑な階層があることは必ずしも悪い設計を意味するわけではありませんが、Dに直接注入するのではなく、TimeFactoryをボトムアップで(AからDに)注入する必要がある場合は、おそらく依存性注入の方法に問題があります。

シングルトン?結構です。アプリケーションコンテキスト全体で共有するインスタンスが1つだけ必要な場合(Infector ++のようなDIのIoCコンテナーを使用するには、TimeFactoryを単一のインスタンスとしてバインドする必要があるだけです)、次の例(ちなみにC++ 11ですが、C++です。すでにC++ 11になっていますか?Leak-Freeアプリケーションを無料で入手できます):

Infector::Container ioc; //your app's context

ioc.bindSingleAsNothing<TimeFactory>(); //declare TimeFactory to be shared
ioc.wire<TimeFactory>(); //wire its constructor 

// if you want to be sure TimeFactory is created at startup just request it
// (else it will be created lazily only when needed)
auto myTimeFactory = ioc.buildSingle<TimeFactory>();

IoCコンテナの良い点は、タイムファクトリをDに渡す必要がないことです。クラス "D"にタイムファクトリが必要な場合は、クラスDのコンストラクタパラメータとしてタイムファクトリを指定してください。

ioc.bindAsNothing<A>(); //declare class A
ioc.bindAsNothing<B>(); //declare class B
ioc.bindAsNothing<D>(); //declare class D

//constructors setup
ioc.wire<D, TimeFactory>(); //time factory injected to class D
ioc.wire<B, D>(); //class D injected to class B
ioc.wire<A, B>(); //class B injected to class A

ご覧のとおり、TimeFactoryを1回だけ注入します。 「A」の使い方は?非常にシンプルで、すべてのクラスが注入され、メインでビルドされるか、ファクトリでインスタンス化されます。

auto myA1 = ioc.build<A>(); //A is not "single" so many different istances
auto myA2 = ioc.build<A>(); //can live at same time

クラスAを作成するたびに、自動的に(遅延インスタンス化)Dまでのすべての依存関係が注入され、DにはTimeFactoryが注入されるため、1つのメソッドのみを呼び出すことで、完全な階層の準備が整います(そして、複雑な階層もこのように解決されます)ボイラープレートコードの大量の削除):「new/delete」を呼び出す必要はありません。アプリケーションロジックをグルーコードから分離できるため、これは非常に重要です。

Dは、Dだけが持つことができる情報を使用してTimeオブジェクトを作成できます。

これは簡単です。TimeFactoryには「create」メソッドがあり、別のシグネチャ「create(params)」を使用するだけで完了です。依存関係のないパラメータは、この方法で解決されることがよくあります。これにより、「文字列」や「整数」などを注入する義務がなくなります。これは、追加のボイラープレートを追加するだけだからです。

誰が誰を作成しますか? IoCコンテナーはインスタンスとファクトリーを作成し、ファクトリーは残りを作成します(ファクトリーは任意のパラメーターで異なるオブジェクトを作成できるので、ファクトリーの状態は実際には必要ありません)。ファクトリをIoCコンテナのラッパーとして使用することもできます。一般的に、IoCコンテナのInjectinは非常に悪く、サービスロケータを使用するのと同じです。一部の人々は、IoCコンテナーをファクトリーでラップすることによって問題を解決しました(これは厳密には必要ありませんが、階層がコンテナーによって解決され、すべてのファクトリーがさらに保守しやすくなるという利点があります)。

//factory method
std::unique_ptr<myType> create(params){
    auto istance = ioc->build<myType>(); //this code's agnostic to "myType" hierarchy
    istance->setParams(params); //the customization you needed
    return std::move(istance); 
}

また、依存性注入を悪用しないでください。単純型は、クラスのメンバーまたはローカルスコープ変数にすることができます。これは当たり前のように見えますが、それを可能にするDIフレームワークがあったからといって、「std :: vector」を注入する人を見たことがあります。デメテルの法則を常に覚えておいてください:「本当に必要なものだけを注入する」

1
CoffeDeveloper

A、B、CクラスもTimeインスタンスを作成する必要がありますか、それともクラスDのみを作成する必要がありますか?クラスDだけの場合、AとBはTimeFactoryについて何も知らないはずです。 Cクラス内にTimeFactoryのインスタンスを作成し、それをクラスDに渡します。「インスタンスを作成する」とは、必ずしもCクラスがTimeFactoryのインスタンス化を担当する必要があることを意味するわけではありません。クラスBからDClassFactoryを受け取ることができ、DClassFactoryはTimeインスタンスの作成方法を知っています。

DIフレームワークがないときにもよく使用する手法は、2つのコンストラクターを提供することです。1つはファクトリーを受け入れ、もう1つはデフォルトファクトリーを作成します。 2つ目は通常、保護/パッケージアクセスがあり、主に単体テストに使用されます。

0
rmaruszewski