web-dev-qa-db-ja.com

コンストラクターまたはプロパティセッターを介した依存性注入?

クラスをリファクタリングし、新しい依存関係を追加しています。クラスは現在、コンストラクターで既存の依存関係を取得しています。したがって、一貫性を保つために、コンストラクターにパラメーターを追加します。
もちろん、いくつかのサブクラスに加えて単体テスト用のさらに多くのものがあるので、私はすべてのコンストラクターを一致するように変更するゲームをプレイしています。
セッターでプロパティを使用することは、依存関係を取得するより良い方法だと思い込ませます。注入された依存関係は、クラスのインスタンスを構築するためのインターフェースの一部であるとは思わない。依存関係を追加すると、すべてのユーザー(サブクラスおよび直接インスタンス化するすべてのユーザー)が突然依存関係を知るようになります。それはカプセル化の中断のように感じます。

これは既存のコードのパターンではないようですので、一般的なコンセンサスが何であるか、コンストラクタとプロパティの長所と短所を見つけたいと思っています。プロパティセッターを使用する方が良いですか?

144

まあ、それは依存します:-)。

クラスが依存関係なしにジョブを実行できない場合は、コンストラクターに追加します。クラスneeds新しい依存関係なので、変更によって物事を壊したいです。また、完全に初期化されていないクラスの作成(「2ステップ構成」)は、アンチパターン(IMHO)です。

クラスが依存関係なしで機能する場合、セッターは問題ありません。

124
sleske

クラスのユーザーは、特定のクラスの依存関係を知るために想定です。たとえば、データベースに接続するクラスがあり、永続層の依存関係を注入する手段を提供していなかった場合、ユーザーはデータベースへの接続が利用可能でなければならないことを決して知りません。ただし、コンストラクターを変更すると、永続層に依存関係があることをユーザーに知らせます。

また、古いコンストラクターのすべての使用を変更する必要がないように、単純にコンストラクターチェーンを古いコンストラクターと新しいコンストラクターの間の一時的なブリッジとして適用します。

public class ClassExample
{
    public ClassExample(IDependencyOne dependencyOne, IDependencyTwo dependencyTwo)
        : this (dependnecyOne, dependencyTwo, new DependnecyThreeConcreteImpl())
    { }

    public ClassExample(IDependencyOne dependencyOne, IDependencyTwo dependencyTwo, IDependencyThree dependencyThree)
    {
        // Set the properties here.
    }
}

依存性注入のポイントの1つは、クラスの依存関係を明らかにすることです。クラスの依存関係が多すぎる場合は、リファクタリングが行われるときがあります。クラスのすべてのメソッドがすべての依存関係を使用していますか?そうでない場合は、クラスをどこに分割できるかを確認するための良い出発点です。

20
Scott

もちろん、コンストラクターを置くことは、一度にすべてを検証できることを意味します。読み取り専用フィールドに物を割り当てると、構築時からオブジェクトの依存関係についていくつかの保証があります。

新しい依存関係を追加するのは非常に苦痛ですが、少なくともこの方法では、コンパイラはそれが正しいまで文句を言い続けます。これは良いことだと思います。

17
Joe

オプションの依存関係が多数ある場合(これは既に臭いです)、おそらくセッターインジェクションがその方法です。ただし、コンストラクターの注入により、依存関係が明らかになります。

11
epitka

一般的に好ましいアプローチは、可能な限りコンストラクター注入を使用することです。

コンストラクターインジェクションは、オブジェクトが適切に機能するために必要な依存関係を正確に示します。依存関係が設定されていないため、オブジェクトのメソッドを呼び出すときにオブジェクトを更新し、クラッシュさせるのは面倒です。コンストラクターによって返されるオブジェクトは、動作状態にある必要があります。

コンストラクターを1つだけにして、デザインをシンプルにし、あいまいさを避けます(人間でない場合、DIコンテナーの場合)。

著書「.NETでの依存性注入」でMark Seemannがlocal defaultと呼ぶものがある場合、プロパティ注入を使用できます:依存関係はオプションです。必要に応じて別の呼び出し元を指定する呼び出し元。

(以下の以前の回答)


注入が必須の場合、コンストラクター注入の方が優れていると思います。これによりコンストラクタが追加されすぎる場合は、コンストラクタの代わりにファクトリの使用を検討してください。

セッター注入は、注入がオプションである場合、またはトラフの途中で変更する場合に便利です。私は一般的にセッターが好きではありませんが、それは好みの問題です。

11
Philippe

それは主に個人的な好みの問題です。個人的には、セッターインジェクションを好む傾向があります。これは、実行時に実装を置換できる方法の柔軟性が向上すると考えられるためです。さらに、多くの引数を持つコンストラクターは、私の意見ではきれいではありません。コンストラクターで提供される引数は、オプションではない引数に制限する必要があります。

クラスインターフェイス(API)がタスクを実行するために必要なものが明確である限り、問題ありません。

7
nkr1pt

個人的には、コンストラクタで依存関係を注入するよりもExtract and Override "pattern"を好んでいますが、これは主に質問で概説した理由によるものです。プロパティをvirtualとして設定し、テスト可能な派生クラスの実装をオーバーライドできます。

7
Russ Cam

クラスの依存関係の要件を「強制」するのに役立つため、コンストラクター注入を好みます。 c'torにある場合、コンシューマhasは、アプリをコンパイルするためのオブジェクトを設定します。セッターインジェクションを使用する場合、実行時まで問題があることを知らない可能性があります。オブジェクトによっては、実行が遅れる場合があります。

注入されたオブジェクトが初期化などの一連の作業自体を必要とする可能性がある場合、私はまだセッター注入を時々使用します。

6
ctacke

これは最も論理的だと思われるため、コンストラクター注入を実行します。私のクラスrequiresこれらの依存関係がその仕事をすることを言っているようです。オプションの依存関係の場合、プロパティは妥当と思われます。

また、プロパティインジェクションを使用して、コンテナを使用して作成されたプレゼンター上のASP.NETビューなどへの参照がコンテナにないものを設定します。

カプセル化が壊れるとは思わない。内部の仕組みは内部にとどまる必要があり、依存関係は異なる懸念に対処します。

6
David Kiff

私は最近 状況に遭遇しました クラスに複数の依存関係がありましたが、各実装では必然的に依存関係の1つだけが変更されました。データアクセスとエラーロギングの依存関係はテスト目的でのみ変更される可能性が高いため、これらの依存関係にオプションのパラメーターを追加し、これらの依存関係のデフォルトの実装を提供しました私のコンストラクターコード。このようにして、クラスのコンシューマーによってオーバーライドされない限り、クラスはデフォルトの動作を維持します。

オプションのパラメーターの使用は、.NET 4などのパラメーターをサポートするフレームワークでのみ実行できます(VB.NETには常にありましたが、C#とVB.NETの両方)。もちろん、クラスのコンシューマーによって再割り当て可能なプロパティを使用するだけで同様の機能を実現できますが、コンストラクターのパラメーターに割り当てられたプライベートインターフェイスオブジェクトを持つことによって提供される不変性の利点は得られません。

すべての消費者が提供しなければならない新しい依存関係を導入している場合、これらすべては、コンストラクターとクラスを消費するすべてのコードをリファクタリングする必要があります。上記の私の提案は、現在のコードのすべてにデフォルトの実装を提供する余裕があり、必要に応じてデフォルトの実装をオーバーライドする機能を提供できる場合にのみ適用されます。

3
Ben McCormack

検討する価値のあるオプションの1つは、単純な単一の依存関係から複雑な複数の依存関係を構成することです。つまり、複合依存関係の追加クラスを定義します。これにより、WRTコンストラクターの注入が少し簡単になります(呼び出しごとのパラメーターが少なくなります)。

もちろん、依存関係のある種の論理グループがある場合に最も意味があります。そのため、複合は任意の集約以上のものであり、単一の複合依存に複数の依存がある場合に最も意味があります。長い間存在しており、私が見たもののほとんどはかなりmost意的でした。

個人的には、メソッド/プロパティセッターを使用して依存関係、オプションなどを指定するのが好きです。呼び出し名は、何が起こっているかを説明するのに役立ちます。ただし、this-is-how-to-set-it-up-upスニペットの例を提供し、依存クラスが十分なエラーチェックを行うようにすることをお勧めします。セットアップに有限状態モデルを使用することもできます。

3
Steve314

これは古い投稿ですが、将来必要になる場合は、これが役に立つかもしれません:

https://github.com/omegamit6zeichen/prinject

私も同様のアイデアを持っていて、このフレームワークを思いつきました。おそらく完全ではありませんが、プロパティインジェクションに焦点を当てたフレームワークのアイデアです

1
Markus

コンストラクターインジェクションは依存関係を明示的に明らかにするため、コンストラクターで引数がチェックされるとコードが読みやすくなり、未処理の実行時エラーが発生しにくくなりますが、実際には個人的な意見になります。プロジェクトに応じて、何らかの方法で前後に揺れる傾向があります。私は個人的に、長い引数リストを持つコンストラクターのようなコードの問題を抱えています。とにかくオブジェクトの消費者はオブジェクトを使用するために依存関係を知っているべきだと感じているので、これはプロパティインジェクションを使用するケースを作ります。プロパティインジェクションの暗黙的な性質は好きではありませんが、よりエレガントで、コードがきれいに見えることがわかります。しかし一方で、コンストラクターインジェクションは高度なカプセル化を提供します。私の経験では、デフォルトのコンストラクターは、注意しないとカプセル化されたデータの整合性に悪影響を与える可能性があるため、避けるようにしています。

特定のシナリオに基づいて、コンストラクターまたはプロパティごとにインジェクションを選択します。また、必要だと思われるだけでDIを使用しなければならないと感じないようにしてください。努力と複雑さが利益を上回る場合、パターンを使用する努力の価値がない場合があります。複雑にしないでおく。

0
David Spenard

実装方法によって異なります。実装に入力される値が頻繁に変化しないと感じる場合は、どこでもコンストラクター挿入を好みます。例:会社の戦略がOracleサーバーに対応している場合、コンストラクター注入を介して接続を実現するBeanのdatsource値を構成します。それ以外の場合、私のアプリが製品であり、顧客の任意のデータベースに接続できる可能性がある場合、セッターインジェクションを介してそのようなデータベース構成とマルチブランドの実装を実装します。例を挙げましたが、上記のシナリオを実装するより良い方法があります。

0
ramarao Gangina