web-dev-qa-db-ja.com

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

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

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

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

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

過去数年でフィールドインジェクションからコンストラクターインジェクションに切り替えたので、使用する方がはるかにわかりやすいのですが、自分の視点を再検討する必要があるかと思います。 JMockit(RogérioLiesenfeld) の作者はDIに精通しているので、コンストラクター/セッターの注入に対して非常に強いと感じているため、私のアプローチを確認する義務があります。

68
Eric B.

フィールドインジェクションも少し「遠くで不気味なアクション」が私の好みです。

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アノテーション。これは、私の状態がすべて宣言されているにもかかわらず、外部からエージェントが自動的に状態を入力できることを意味しますprivate。 "

私はこれの動機を理解しています。それは、クラスを適切にセットアップすることに固有のセレモニーの多くを回避する試みです。基本的に、「私はこの定型文をすべて書くのに疲れたので、すべての状態に注釈を付け、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コンテナーが行う必要があるのは、コンストラクターを反映して、新規に必要なオブジェクトを判別することだけです。しかし、その反映はpublicメンバーでファーストクラスの方法で行われています。つまり、メタデータは既にクラスの一部であり、コンストラクターで宣言されていますその明示的な目的は、クラスに必要な依存関係を提供することです

確かにこれは定型句の多くですが、これは言語が設計された方法です。注釈は、言語自体に組み込まれているはずの何かに対する汚いハックのように見えます。

51
Robert Harvey

テストの初期化ボイルプレートが少ないという議論は有効ですが、考慮すべき他の懸念事項があります。まず、質問に答える必要があります。

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

フィールドインジェクションを使用すると、リフレクションを使用してオブジェクトをインスタンス化し、これらの特定のインジェクションアノテーションをサポートする依存性インジェクション環境にクラスの compatibility を絞り込むことができます。 Java言語に基づく一部のプラットフォームはリフレクション( [〜#〜] gwt [〜#〜] )さえサポートしていないため、フィールド注入クラスは互換性がありません彼らと。

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

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

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

  1. 別のプラットフォームでコードの一部を使用する確率はどのくらいですか?
  2. いくつのユニットテストが書かれますか?
  3. 新しいリリースごとにどれだけ早く準備する必要がありますか?
  4. ...

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

フィールド注入に対する多くの議論。

30

フィールド注入は、私から明確な「いいえ」の投票を取得します。

ロバート・ハーヴェイのように、それは私の好みには少しオートマジックです。私は暗黙的なコードよりも明示的なコードを好み、コードを理解し、推論することを困難にするため、明確な利点を提供する場合にのみ間接参照を許容します。

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

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

私のテストは非常に単純であることが好きです。 IoCコンテナーまたは他のDIフレームワークをセットアップして、テスト対象のクラスをセットアップするという間接的な作業を行う必要がありません。ぎこちなく感じます。

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

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

22
Marjan Venema

さて、これは論争の的となっている議論のようです。最初に対処する必要があるのは、フィールド注入がセッター注入と異なることです。 Fieldと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. クラスの依存関係は明白です:クラスの依存関係 コンストラクターで明白です しかしhidedフィールド/セッターインジェクション。私のクラスは、フィールド/セッター注入を備えた「ブラックボックス」のように感じます。そして、(私の経験では)多くの依存関係を持つクラスを気にしない開発者の傾向があります。これは、コンストラクターインジェクションを使用してクラスをモックしようとするときに明らかです(次の項目を参照)。開発者を悩ませている問題は、おそらく解決されるでしょう。また、依存関係が多すぎるクラスは、おそらく単一責任原則(SRP)に違反しています。
  3. モックするのが簡単で信頼できるフィールドインジェクションと比較して 、ユニットテストでモックするのは簡単です。クラス内で宣言されたプライベートBeanに到達するには、コンテキストモックまたはリフレクション技術が必要であり、 それによってだまされる にはなりません。クラスをインスタンス化し、モックBeanを渡すだけです。理解しやすく、簡単です。
  4. コード品質のツールはあなたを助けることができます:Sonarのようなツールは、クラスが複雑になりすぎることを警告することができます。複雑なクラスで、リファクタリングが必要です。クラスがフィールド/セッターインジェクションを使用している場合、このツールは同じ問題を特定できません。
  5. より良く独立したコード:少ない@AutoWiredコード間で広がり、コードはフレームワークにあまり依存しません。プロジェクトのDIフレームワークを単純に変更する可能性は、それを正当化しないことを知っています(誰がそうするのでしょうか?)。しかし、これは私にとって、より良いコードを示しています(EJBとルックアップを使用して 難しい方法 でこれを学びました)。また、Springを使用すると、@AutoWiredコンストラクター注入を使用した注釈。
  6. より多くの注釈が常に正しいとは限りませんFor いくつか 理由 、私は本当に使用しようとしますアノテーションが本当に役立つ場合のみ。
  7. 注射を不変にすることができます。不変性は 大歓迎 機能です。

詳細については、Spring Blogの この古い(ただし関連性のある)記事 をお勧めします。これにより、セッターインジェクションを使用する理由と、コンストラクターインジェクションの使用が推奨されます。

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

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

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

最終的な考え

Spring team で説明されているように、コンストラクターの利点が本当にわからない場合は、セッター(フィールドではなく)をコンストラクターの注入と混合するという考えがオプションかもしれません。

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

ただし、フィールドインジェクションはおそらく3つのうちの1つ 避けるべき であることに注意してください。

8
Dherik