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


151

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

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

回答:


126

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

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

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


11
多くの場合、Null Objectパターンを使用し、コンストラクターへの参照を要求することにこだわることが望ましいと思います。これにより、すべてのnullチェックとサイクロマティックの複雑化が回避されます。
マークリンデル

3
@マーク:良い点。ただし、問題は既存のクラスに依存関係を追加することでした。次に、引数なしのコンストラクターを保持することで、下位互換性が可能になります。
sleske 2010年

依存関係が機能する必要がある場合はどうでしょうか。ただし、その依存関係のデフォルトの注入で通常は十分です。次に、その依存関係は、プロパティまたはコンストラクターのオーバーロードによって「オーバーライド可能」でなければなりませんか?
Patrick Szalapski、2010

@Patrick:「クラスは依存関係なしにその仕事をすることができない」とは、合理的なデフォルトがないことを意味しました(たとえば、クラスはDB接続を必要とします)。あなたの状況では、どちらもうまくいくでしょう。複雑さを軽減するため、私は通常コンストラクターアプローチを選択します(たとえば、セッターが2回呼び出された場合はどうなりますか?)
sleske 2010

1
これについての良い記事は、ここexplorerjava.com/different-types-of-bean-injection-in-spring
Eldhose Abraham

21

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

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

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つは、クラスの依存関係を明らかにすることです。クラスの依存関係が多すぎる場合は、リファクタリングを行う時期かもしれません。クラスのすべてのメソッドがすべての依存関係を使用していますか?そうでない場合は、クラスを分割できる場所を確認するための出発点として最適です。


もちろん、コンストラクターの連鎖は、新しいパラメーターに適切なデフォルト値がある場合にのみ機能します。しかし、そうでなければ、とにかく物事を壊すことを避けることはできません...
sleske

通常、デフォルトパラメータとして、依存関係注入の前にメソッドで使用していたものをすべて使用します。理想的には、これにより、クラスの動作が変更されないため、新しいコンストラクタの追加がクリーンなリファクタリングになります。
ドクターブルー

1
データベース接続などの依存関係のリソース管理について、私はあなたの意見を述べます。私の場合の問題は、依存関係を追加するクラスにいくつかのサブクラスがあることです。プロパティがコンテナーによって設定されるIOCコンテナーの世界では、セッターを使用することで、すべてのサブクラス間のコンストラクターインターフェイスの重複に対するプレッシャーが少なくとも軽減されます。
Niall Connaughton

17

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

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


これにプラス1つ。さらに、これにより循環依存の危険が大幅に減少します...
gilgamash

11

オプションの依存関係が多数ある場合(既ににおいがしている)、おそらくセッター注入が適切です。ただし、コンストラクター注入により、依存関係がより明確になります。


11

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

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

コンストラクターは1つだけにしてください。これにより、設計がシンプルになり、あいまいさ(人間ではない場合でも、DIコンテナー)が回避されます。

Mark Seemannの著書 『Dependency Injection in .NET』でMark Seemannがローカルデフォルトと呼んでいるものがある場合は、プロパティインジェクションを使用できます。適切に機能する実装を提供できるが、呼び出し元が別のものを指定できるようにするため、依存関係はオプションです。必要。

(以前の回答は以下)


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

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


通常、注入の途中での変更は悪いスタイルだと思います(オブジェクトに非表示の状態を追加しているため)。もちろん、例外のないルールはありません...
sleske

うん、そういうわけで、セッターはあまり好きではないと言いました...変更できないので、コンストラクターアプローチが好きです。
フィリップ

「これによりコンストラクターが多すぎる場合は、コンストラクターの代わりにファクトリーを使用することを検討してください。」基本的に、実行時の例外をすべて延期し、問題が発生してサービスロケータの実装で行き詰まる可能性もあります。
MeTitus 16

@Marcoそれは私の以前の答えであり、あなたは正しいです。コンストラクタがたくさんある場合、クラスがあまりにも多くのことをしていると主張します:-)または、抽象ファクトリを検討してください。
フィリップ

7

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

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


なぜ反対票を投じているのか具体的に教えてください。
nkr1pt 2009年

3
はい、引数の多いコンストラクタは悪いです。これが、多くのコンストラクタパラメータを持つクラスをリファクタリングする理由です:-)。
sleske 2009年

10
@ nkr1pt:ほとんどの人(私も含む)は、注入が行われない場合に実行時に失敗するクラスを作成できる場合、セッター注入は悪いことであることに同意します。したがって、個人的な趣味であるというあなたの発言には誰かが反対したと思います。
sleske 2009年

7

個人的には、主に質問で概説されている理由により、コンストラクターに依存関係を注入するよりも、抽出と上書きの「パターン」を好みます。プロパティをとして設定し、virtualテスト可能な派生クラスの実装をオーバーライドできます。


1
パターンの正式名称は「テンプレートメソッド」だと思います。
davidjnelson 2012

6

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

初期化のように、注入されたオブジェクト自体が大量の作業を必要とする可能性があるときに、私はまだセッター注入を時々使用しています。


6

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

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

私はそれがカプセル化を壊すとは思わない。内部の仕組みは内部のままにしておく必要があり、依存関係は別の問題に対処します。


ご回答有難うございます。確かにコンストラクターが人気のある答えのようです。しかし、私はそれが何らかの方法でカプセル化を壊すと思います。依存関係の注入前に、クラスはその仕事を行うために必要な具体的な型を宣言してインスタンス化します。DIを使用すると、サブクラス(および手動のインスタンス化プログラム)は、基本クラスが使用しているツールを認識できるようになります。新しい依存関係を追加し、依存関係自体を使用する必要がない場合でも、すべてのサブクラスからインスタンスをチェーンする必要があります。
Niall Connaughton、

このサイトの例外のために、長い回答を書いて、それを失っただけです!! :(要約すると、ベースクラスは通常、ロジックを再利用するために使用されます。このロジックはサブクラスに非常に簡単に入る可能性があります...したがって、ベースクラスとサブクラスを考えることができます=複数の外部オブジェクトに依存する1つの懸念、 。。別の仕事をあなたが依存関係を持っているという事実を、あなたは以前に非公開にしているだろう何かを公開する必要が意味するものではありません
デビッドKiff

3

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

もちろん、依存関係のある種の論理的なグループ化がある場合、それは最も理にかなっています。そのため、複合は任意の集合体以上であり、単一の複合依存に複数の依存がある場合、それは最も理にかなっていますが、パラメータブロック「パターン」は長い間存在し、私が見たもののほとんどはかなり恣意的でした。

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


3

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

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

これらすべてが言われていることですが、すべてのコンシューマーが提供する必要がある新しい依存関係を導入する場合、コンストラクターとクラスをコンシューマー化するすべてのコードをリファクタリングする必要があります。上記の私の提案が実際に当てはまるのは、現在のすべてのコードに対してデフォルトの実装を提供できるという贅沢がある一方で、必要に応じてデフォルトの実装をオーバーライドする機能を提供できる場合だけです。


1

これは古い投稿ですが、将来必要になった場合は、おそらく役に立ちます。

https://github.com/omegamit6zeichen/prinject

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


0

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


1
社内でコーディングする場合でも、私は常に、再頒布可能にすることを目的としたコードを開発する独立した請負業者であるという観点からコーディングします。オープンソースになると思います。このようにして、コードがモジュール式でプラグ可能であり、SOLIDの原則に従っていることを確認しています。
フレッド

0

コンストラクターインジェクションは、依存関係を明示的に明らかにし、引数がコンストラクターでチェックされる場合、コードを読みやすくし、未処理の実行時エラーを起こしにくくしますが、実際には個人的な意見であり、DIを使用すればするほど、多くのことを実行できますプロジェクトに応じて、一方または他方を前後に振る傾向があります。私は個人的に、引数のリストが長いコンストラクターのようなコードのにおいに問題があり、オブジェクトのコンシューマーはオブジェクトを使用するために依存関係を知っている必要があると感じているので、これはプロパティインジェクションを使用するケースになります。プロパティインジェクションの暗黙的な性質は好きではありませんが、より洗練されているため、見た目がすっきりしたコードになります。しかし一方で、コンストラクターインジェクションはより高度なカプセル化を提供します。

特定のシナリオに基づいて、賢明なコンストラクタまたはプロパティによる注入を選択してください。また、必要と思われるだけでDIを使用する必要があると感じないでください。これにより、悪意のあるデザインやコードの臭いが防止されます。努力と複雑さがメリットを上回っている場合は、パターンを使用する価値がない場合があります。単純にする。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.