依存性注入; 定型コードを減らす良い習慣


25

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

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

良い例に勝るものはありません。

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

詳細(関連があるかどうかはわかりません):Timeオブジェクトは非常に複雑です。Timeにはさまざまな形式があり、それらの間の変換は線形でも簡単でもないためです。各「Time」には、変換を処理するSynchronizerが含まれており、適切に初期化された同じシンクロナイザーを使用するために、TimeFactoryを使用します。TimeFactoryにはインスタンスが1つしかなく、アプリケーション全体であるため、シングルトンの資格がありますが、可変なので、シングルトンにしたくありません

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

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

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

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

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

どう思いますか ?


5
私が理解したように、プログラマは設計関連の質問に向いており、stackoverflowは「あなたがコンパイラの前にいるとき」-質問のためであるため、この質問をstackoverflowではなくプログラマに投稿しました。私が間違っている場合は、事前に申し訳ありません。
ディナイズ

2
アスペクト指向プログラミングは、またこの作業に適しています- en.wikipedia.org/wiki/Aspect-oriented_programming
EL Yusubov

クラスB、C、DのクラスAファクトリーですか?そうでない場合、なぜそれらのインスタンスを作成しますか?クラスDのみがTimeオブジェクトを必要とする場合、なぜ他のクラスにTimeFactoryを注入するのですか?クラスDには本当にTimeFactoryが必要ですか、それともTimeのインスタンスだけを注入できますか?
シモラマン

Dは、Dのみが持っているはずの情報を使用して、Timeオブジェクトを作成する必要があります。あなたのコメントの残りについて考えなければなりません。私のコードの「誰が誰を作成しているか」に問題がある可能性があります
-Dinaiz

「誰が誰を作成しているか」はIoCコンテナによって解決されます。詳細については私の答えをご覧ください。
GameDeveloper

回答:


23

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

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

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


完全に正しい。「プログラムの可読性は、桁違いに低下します。」:それはまさにそうです。私がまだ工場を使いたいなら、それをシングルトンにするのは悪くないですか?
ディナイズ

3
@Dinaiz:アイデアはTime、プログラムとプログラムの他の部分との間の密結合を受け入れることです。ですから、に対する密結合も受け入れることができますTimeFactory。しかし、私が避けたいのはTimeFactory状態(たとえば、ロケール情報など)を持つ単一のグローバルオブジェクトを持つことです。これは、プログラムに厄介な副作用を引き起こし、一般的なテストと再利用を非常に困難にする可能性があります。ステートレスにするか、シングルトンとして使用しないでください。
Doc Brown

実際には状態を持たなければならないので、「シングルトンとして使用しない」と言うとき、「依存性注入を使用し続ける、つまりインスタンスをそれを必要とするすべてのオブジェクトに渡す」ことを意味しますか?
ディナイズ

1
@Dinaiz:正直なところ、それは、状態の種類、使用Timeしているプログラムの部分の種類と数、およびにTimeFactory関連する将来の拡張に必要な「進化可能性」の程度などに依存TimeFactoryします。
Doc Brown

OK私は依存性を注入しようとしますが、それほど多くの定型文を必要としないより賢い設計をします!おかげでたくさんの文書」
ディナイズ

4

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

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

必要なのは、クラスAに注入されたクラスBです。クラスBに注入されたクラスC。クラスCに注入されたクラスDです。


DIは読みやすさに関するものです。メソッドの途中に「魔法のように」グローバル変数が表示されている場合、(メンテナンスの観点から)コードを理解することは役に立ちません。この変数はどこからですか?誰がそれを所有していますか?誰が初期化するのですか?など... 2番目の点については、おそらく正しいでしょうが、私の場合には当てはまらないと思います。返信に時間を割いていただきありがとうございます
ディナイズ

DIは、主に「新規/削除」がコードから魔法のように取り除かれるため、可読性を高めます。ダイナミックアロケーションコードについて警告する必要がない場合は、読みやすくなります。
GameDeveloper

また、これにより、依存関係が「どのように」作成されるかを推測することができず、「必要なだけ」であるため、コードが少し強くなることを忘れてください。それにより、クラスのメンテナンスが容易になります。私の答え
-GameDeveloper

3

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

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

依存性注入を行いたい場合は、注釈を使用してパラメーターを自動で割り当てられるスプリングまたはその他の依存性注入フレームワークを確認することをお勧めします。


SpringはJava用で、C ++を使用しています。しかし、なぜ2人があなたに投票したのですか?あなたの投稿には私が同意しない点がありますが、それでも
...-ディナイズ

おそらく、Springについての言及と同様に、ダウンボート自体が質問に答えなかったためだと思います。しかし、シングルトンを使用するという提案は私の頭の中では有効なものであり、質問に答えられないかもしれません-しかし、有効な代替案を提案し、興味深い設計上の問題を持ち出します。このため、私は+1しました。
ロンドンのファーガス13年

1

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

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

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

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が注入されます。大量のボイラープレートコードを削除する): "new / delete"を呼び出す必要はありません。アプリケーションロジックをグルーコードから分離できるため、これは非常に重要です。

Dは、Dのみが持つことができる情報を持つTimeオブジェクトを作成できます。

それは簡単です。TimeFactoryには「作成」メソッドがあり、別の署名「create(params)」を使用するだけで完了です。依存関係のないパラメーターは、多くの場合この方法で解決されます。また、追加のボイラープレートを追加するだけなので、「文字列」や「整数」などを注入する義務がなくなります。

誰が誰を作成しますか?IoCコンテナーはインスタンスとファクトリーを作成し、ファクトリーは残りを作成します(ファクトリーは任意のパラメーターで異なるオブジェクトを作成できるため、ファクトリーの状態は実際には必要ありません)。IoCコンテナーのラッパーとしてファクトリーを引き続き使用できます。一般的に、IoCコンテナーのインジェクションは非常に悪く、サービスロケーターの使用と同じです。一部の人々は、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"を注入しているのを見ました。デメテルの法則を常に覚えておいてください:「本当に必要なものだけを注入してください」


わあ、前にあなたの答えが見えなかった、すみません!非常に有益な先生!賛成
ディナイズ

0

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

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


-1

さらに別のC ++依存性注入フレームワークを実装しました。これは最近、ブースト用に提案されました– https://github.com/krzysztof-jusiak/di –ライブラリはマクロレス(無料)、ヘッダーのみ、C ++ 03 / C ++ 11 / C ++ 14ライブラリは、タイプセーフ、コンパイル時間、マクロフリーコンストラクター依存関係注入を提供します。


3
P.SEの古い質問に答えることは、プロジェクトを宣伝するのに不十分な方法です。いくつかの質問に何らかの関連性があるかもしれない何万ものプロジェクトがあることを考慮してください。すべての著者がここに投稿した場合、サイトの回答品質は急落します。
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.