依存性注入:フィールド注入vsコンストラクター注入?


62

私はこれが熱い議論であることを知っており、意見はベストアプローチの実践に関して時間とともに変化する傾向があります。

コンストラクターインジェクションの利点についてさまざまなブログ(例:petrikainulainenschauderhaftfowler)を読み始めるまで、クラスではフィールドインジェクションのみを使用していました。それ以来、必要な依存関係にはコンストラクター注入を使用し、オプションの依存関係にはセッター注入を使用するように方法を切り替えました。

しかし、私は最近、モック作成フレームワークであるJMockitの作成者との議論に参加しました。彼は、コンストラクターとセッターの注入を悪い習慣と見なし、JEEコミュニティが彼に同意することを示しています。

今日の世界では、注射を行う好ましい方法はありますか?フィールドインジェクションは好ましいですか?

ここ数年フィールドインジェクションからコンストラクターインジェクションに切り替えたため、使用するのがはるかに明確になりましたが、自分の視点を再検討すべきかどうか疑問に思っています。JMockit(RogérioLiesenfeld)の著者はDIに精通しているため、コンストラクター/セッターインジェクションに対して非常に強く感じているので、私のアプローチを検討する義務があります。



2
コンストラクター注入またはセッター注入の使用が許可されていない場合、どのように依存関係をクラスに渡すのですか?Mockitの人との会話がプライベートでない限り、議論にリンクした方がいいかもしれません。
ロバートハーヴェイ

2
@DavidArno:不変クラスでは、コンストラクター
ロバートハーヴェイ

1
@Davkdが説明しているのは、貧血領域モデルです。確かに支持者がいますが、実際にはオブジェクト指向ではありません。データとそのデータに作用する機能が共存する場所。
リチャードティングル

3
@David関数型プログラミングには何も問題はありません。しかし、javaは基本的にオブジェクト指向になるように設計されているので、注意を怠る常に戦い続けて貧弱なドメインモデルになります。javaはない(ラムダのは、機能ベースのプログラミングの要素を追加しているにもかかわらず)であることのために使用されるべき機能指向のコードが、適切なツールと間違って何も、ありません
リチャード・チンクル

回答:


48

フィールドインジェクションは、私にとっては「遠距離での不気味なアクション」です。

Googleグループの投稿で提供した例を考えてみましょう。

public class VeracodeServiceImplTest {


    @Tested(fullyInitialized=true)
    VeracodeServiceImpl veracodeService;
    @Tested(fullyInitialized=true, availableDuringSetup=true)
    VeracodeRepositoryImpl veracodeRepository;
    @Injectable private ResultsAPIWrapper resultsApiWrapper;
    @Injectable private AdminAPIWrapper adminApiWrapper;
    @Injectable private UploadAPIWrapper uploadApiWrapper;
    @Injectable private MitigationAPIWrapper mitigationApiWrapper;

    static { VeracodeRepositoryImpl.class.getName(); }
...
}

つまり、基本的にあなたが言っているのは、「私はこのクラスにプライベート状態があり、それに@injectable注釈を付けています。つまり、私の状態はすべてプライベートであると宣言されていても、外部から何らかのエージェントによって自動的に状態が設定されます

私はこれの動機を理解しています。クラスを適切に設定することに固有の式典の多くを回避する試みです。基本的に、「ボイラープレートをすべて書くのにうんざりしているので、すべての状態に注釈を付けて、DIコンテナに設定してもらいます」ということです。

それは完全に有効な視点です。しかし、これはおそらく回避すべきでない言語機能の回避策でもあります。また、なぜそこで停止するのですか?従来、DIは、コンパニオンインターフェイスを持つ各クラスに依存していました。アノテーションを使用してこれらのインターフェースもすべて削除しないのはなぜですか?

代替案を検討してください(これはC#になります。これは私がよく知っているためですが、Javaにはおそらく完全に同等のものがあります)。

public class VeracodeService
{        
    private readonly IResultsAPIWrapper _resultsApiWrapper;
    private readonly IAdminAPIWrapper _adminApiWrapper;
    private readonly IUploadAPIWrapper _uploadApiWrapper;
    private readonly IMitigationAPIWrapper _mitigationApiWrapper;

    // Constructor
    public VeracodeService(IResultsAPIWrapper resultsApiWrapper, IAdminAPIWrapper adminApiWrapper, IUploadAPIWrapper uploadApiWrapper,         IMitigationAPIWrapper mitigationApiWrapper)
    {
         _resultsAPIWrapper = resultsAPIWrapper;
         _adminAPIWrapper = adminAPIWrapper;
         _uploadAPIWrapper = uploadAPIWrapper;
         _mitigationAPIWrapper = mitigationAPIWrapper;
    }
}

すでにこのクラスについていくつかのことを知っています。これは不変のクラスです。状態は、コンストラクター(この特定のケースでは参照)でのみ設定できます。そして、すべてがインターフェースから派生しているため、コンストラクターで実装を交換できます。ここで、モックが入ります。

これで、DIコンテナーが行う必要があるのは、コンストラクターを反映して、更新する必要のあるオブジェクトを判別することだけです。しかし、その反映は一流の方法で、公開メンバーに対して行われています。つまり、メタデータは既にクラスの一部であり、コンストラクタで宣言されており、その目的はクラスに必要な依存関係を提供することです。

これは定型的なものですが、これが言語の設計方法です。アノテーションは、言語自体に組み込まれるべきものに対する汚いハックのようです。


あなたの投稿を間違って読んでいない限り、あなたは実際に例を正しく見ていません。私の実装はVeracodeService、あなたが書いたものとほぼ同じです(ただし、JavaとC#では)。VeracodeServiceImplTest実際にユニットテストクラスです。@Injectableフィールドは、コンテキスト内に挿入され、本質的にモックオブジェクトです。@Testedフィールドは、定義されたDIコンストラクタでオブジェクト/クラスです。フィールド注入よりもコンストラクタ注入を好むというあなたの見解に同意します。しかし、私が述べたように、JMockitの作者は反対のことを感じていると私は理由を理解しようとしています
エリック・B.

あなたが見れば最初の投稿という議論では、あなたは私のレポクラスではほぼ正確に同じDEFNを持って表示されます。
エリックB.

テストクラスについてのみ話している場合は、問題ではないかもしれません。なぜなら、クラスをテストするからです。
ロバートハーヴェイ

いいえ-私はテストクラスについて話していません。プロダクションクラスについて話している。フレームワークはモック/テストフレームワークであるため、ディスカッショングループの例は単体テストであることが発生します。(モックフレームワークがコンストラクターインジェクションをサポートする必要がある場合は、議論全体が中心になります)
Eric B.

3
+1:はい、C#バージョンには魔法はありません。これは標準クラスであり、依存関係があり、クラスが使用される前にすべてが1つの時点で注入されます。このクラスは、DIコンテナーで使用することも使用しないこともできます。クラスの何もそれをIOCまたは「自動依存性注入」フレームワークと言いません。
バイナリの心配

27

少ないテスト初期化ボイラープレートの議論は有効ですが、考慮しなければならない他の懸念があります。まず、質問に答える必要があります。

クラスをリフレクションのみでインスタンス化できるようにしますか?

フィールド注入を使用すると、リフレクションを使用してオブジェクトをインスタンス化し、これらの特定の注入注釈をサポートする依存性注入環境へのクラスの互換性を絞り込むことができます。Java言語に基づくプラットフォームの中には、リフレクション(GWT)をサポートしないものもあるため、フィールド注入クラスはそれらと互換性がありません。

2番目の問題はパフォーマンスです。コンストラクター呼び出し(直接またはリフレクションによる)は、多数のリフレクションフィールドの割り当てよりも常に高速です。依存性注入フレームワークは、リフレクション分析を使用して、依存関係ツリーを構築し、リフレクションコンストラクターを作成する必要があります。このプロセスにより、パフォーマンスがさらに低下します。

パフォーマンスは保守性に影響ます。各テストスイートをある種の依存性注入コンテナで実行する必要がある場合、数千のユニットテストのテスト実行には数十分かかる場合があります。コードベースのサイズによっては、これが問題になる場合があります。

これはすべて多くの新しい質問を引き起こします:

  1. 別のプラットフォームでコードの一部を使用する可能性はどのくらいですか?
  2. 単体テストはいくつ作成されますか?
  3. 新しいリリースを準備するのにどれくらいの時間が必要ですか?
  4. ...

一般的に、プロジェクトが大きくて重要であるほど、これらの要因はより重要になります。また、品質面では、一般的に、互換性、テスト容易性、保守性をコードの高さに維持したいと考えています。哲学的な観点から、フィールドインジェクションはカプセル化を破ります。カプセル化は、Javaの主なパラダイムであるオブジェクト指向プログラミングの4つの基本の1つです。

フィールドインジェクションに反対する多くの議論。


3
+1神経を打った。クラスをインスタンス化するために、リフレクションまたはDI / IoCフレームワークが必要ではありません。アムステルダムからパリに行くためにローマに車で行くようなものです。DIが大好きです。フレームワークについてはよくわかりません。
マルジャンヴェネマ

1
素晴らしい議論。自分の考えの多くを言葉にしますが、他の人が同じように感じているのを見てうれしいです。私がフィールドインジェクションを見つけるのに使用したのと同じくらい驚くべきことですが、それが「簡単」だったという理由だけで、私はそれをとても気に入ったと思います。コンストラクターのインジェクションは、今では非常に明確になりました。
エリックB.

19

フィールドインジェクションは、私から明確な「いいえ」の票を得ます。

ロバート・ハーヴェイのように、それは私の好みには少し自動化されすぎています。暗黙的なコードよりも明示的なコードの方が好きで、コードの理解と推論が難しくなるため、明確な利点が得られる場合にのみ間接化を許容します。

MaciejChałapukのように、クラスをインスタンス化するためにリフレクションまたはDI / IoCフレームワークが必要ではありません。アムステルダムからパリに行くためにローマに車で行くようなものです。

私はDIとそれがもたらすすべての利点が大好きです。クラスをインスタンス化するためにフレームワークが必要かどうかはわかりません。特に、IoCコンテナーまたは他のDIフレームワークがテストコードに与える影響はありません。

私は自分のテストが非常に簡単であることを好みます。IoCコンテナーまたは他のDIフレームワークをセットアップする間接的な手順を経て、テスト対象のクラスをセットアップする必要はありません。気まずいだけです。

そして、それは私のテストをフレームワークのグローバルな状態に依存させます。つまり、クラスXのインスタンスを異なる依存関係でセットアップする必要がある2つのテストを並行して実行することはできなくなりました。

少なくともコンストラクターとセッターインジェクションでは、テストでフレームワークやリフレクションを使用しないオプションがあります。


たとえば、Mockito mockito.orgを使用する場合、フィールドインジェクションを使用する場合でもDI / IoCフレームワークは必要なく、テストを並行して実行できます。
ハンスピーターStörr15年

1
@hstoerrニース。しかし、それは... Mockitoは、リフレクションを使用している(またはそのJavaの当量が呼ばれているものは何でも)ことを意味する必要があります
マージャンVenema氏

5

まあ、これは論争的な議論のようです。最初に対処する必要があるのは、フィールド注入がセッター注入と異なることです。Field and Setterインジェクションは同じものだと思っている人たちと既に仕事をしています。

したがって、明確にするために:

フィールド注入:

@Autowired
private SomeService someService;

セッター注入:

@Autowired
public void setSomeService(SomeService someService) {
    this.someService = someService;
}

しかし、私にとっては、彼らは同じ懸念を共有しています。そして、私のお気に入りのコンストラクター注入:

@AutoWired
public MyService(SomeService someService) {
    this.someService = someService;
}

コンストラクター注入は、セッター/フィールド注入よりも優れていると信じる次の理由があります(私のポイントを示すために、いくつかのSpringリンクを引用します)。

  1. 循環依存関係の防止:@Tomaszが説明したように、SpringはBean間の循環依存関係を検出できます。それを言っているSpringチームも参照してください。
  2. クラスの依存関係は明白です:クラスの依存関係は、コンストラクタで明白ですが、隠してフィールド/セッターインジェクションと。クラスはフィールド/セッターインジェクションを備えた「ブラックボックス」のように感じます。そして、(私の経験では)多くの依存関係を持つクラスを気にしない開発者の傾向があります。これは、コンストラクターインジェクションを使用してクラスをモックしようとすると明らかです(次の項目を参照)。開発者を悩ます問題は、ほとんど解決される可能性があります。また、依存関係が多すぎるクラスは、おそらく単一責任原則(SRP)に違反しています。
  3. モックに簡単で信頼性の高いフィールドの注入に比べて、それはあなたが、クラス内で宣言プライベートビーンに到達するためにどのようなコンテキストモックまたは反射技術を必要としないので、ユニットテストでモックする方が簡単だとあなたがされることはありません、それにだまさ。クラスをインスタンス化し、モックされたBeanを渡すだけです。わかりやすく簡単です。
  4. コード品質のツールが役立ちます。多くのパラメーターを持つクラスはおそらくあまりにも複雑なクラスであり、リファクタリングが必要なため、Sonarなどのツールはクラスが複雑になりすぎることを警告できます。クラスがフィールド/セッターインジェクションを使用している場合、このツールは同じ問題を特定できません。
  5. より優れた独立したコードコード@AutoWired間の広がりが少ないため、コードはフレームワークに依存しなくなります。プロジェクトのDIフレームワークを単純に変更する可能性は、それを正当化するものではないことを知っています(誰がそうするのですか?)。しかし、これは、私にとって、より良いコードを示しています(EJBとルックアップで難しい方法でこれを学びました)。また、Springでは、@AutoWiredコンストラクター注入を使用して注釈を削除することもできます。
  6. より多くの注釈は常に正しい答えではありませんについていくつかの理由から、私は本当に彼らが本当に便利ているときのみ、注釈を使用してみてください。
  7. 注入を不変にすることができます。不変性は非常に歓迎される機能です。

詳細をお探しの場合は、Spring Blogのこの古い(しかし関連性のある)記事で、セッターインジェクションを使用する理由とコンストラクターインジェクションの使用に関する推奨事項をお勧めします。

セッターインジェクションは、予想よりもはるかに頻繁に使用されます。これは、一般的なSpringのようなフレームワークが、コンストラクターインジェクションよりもセッターインジェクションによる設定に適しているという事実です。

そして、コンストラクターがアプリケーションコードにより適していると彼らが信じる理由についてのこのメモ:

コンストラクタインジェクションは、フレームワークコードよりもアプリケーションコードではるかに使いやすいと思います。アプリケーションコードでは、構成する必要があるオプション値の必要性は本質的に少ない

最終的な考え

コンストラクターの利点が本当にわからない場合は、Springチームが説明しているように、フィールドではなくセッターをコンストラクター注入と組み合わせるというアイデアが選択肢になるかもしれません。

コンストラクターベースとセッターベースの両方のDIを混在させることができるため、必須の依存関係にはコンストラクター引数を使用し、オプションの依存関係にはセッターを使用することをお勧めします

ただし、フィールドインジェクションは、おそらく3つのうち回避すべきものであることを忘れないでください。


-5

クラスの存続期間中に依存関係が変わる可能性はありますか?その場合は、フィールド/プロパティの注入を使用します。それ以外の場合は、コンストラクター注入を使用します。


2
これは実際には何も説明しません。代わりに適切な方法で実行できるのに、なぜプライベートフィールドを挿入するのですか?
ロバートハーベイ

プライベートフィールドについてはどこに記載されていますか?クラスのパブリックインターフェイスについて話している/
ダレンヤング

この質問には、メソッドインジェクションとコンストラクターインジェクションに関する議論はありません。JMockitの作者は、両方とも悪いと考えています。質問のタイトルを見てください。
ロバートハーベイ

質問は、フィールドインジェクションとコンストラクターインジェクションとは言いませんか?
ダレンヤング

1
フィールドインジェクションは完全に異なる動物です。プライベートメンバーに値を突っ込んでいます。
ロバートハーベイ
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.