私はこれが熱い議論であることを知っており、最善のアプローチの実践に関しては、意見が時間とともに変化する傾向があります。
さまざまなブログ(例: petrikainulainen と schauderhaft と fowler )について読み始めるまでは、クラスでフィールドインジェクションのみを使用していましたコンストラクター注入の利点。それ以来、必要な依存関係にはコンストラクター注入を使用し、オプションの依存関係にはセッター注入を使用するように方法論を切り替えました。
ただし、 私は最近議論に巻き込まれました JMockitの作者-モックフレームワーク-で、彼はコンストラクターとセッターの注入を悪い習慣と見なしており、JEEコミュニティーが彼に同意していることを示しています。
今日の世界では、注射を行うための好ましい方法はありますか?フィールドインジェクションは推奨されますか?
過去数年でフィールドインジェクションからコンストラクターインジェクションに切り替えたので、使用する方がはるかにわかりやすいのですが、自分の視点を再検討する必要があるかと思います。 JMockit(RogérioLiesenfeld) の作者はDIに精通しているので、コンストラクター/セッターの注入に対して非常に強いと感じているため、私のアプローチを確認する義務があります。
フィールドインジェクションも少し「遠くで不気味なアクション」が私の好みです。
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メンバーでファーストクラスの方法で行われています。つまり、メタデータは既にクラスの一部であり、コンストラクターで宣言されていますその明示的な目的は、クラスに必要な依存関係を提供することです
確かにこれは定型句の多くですが、これは言語が設計された方法です。注釈は、言語自体に組み込まれているはずの何かに対する汚いハックのように見えます。
テストの初期化ボイルプレートが少ないという議論は有効ですが、考慮すべき他の懸念事項があります。まず、質問に答える必要があります。
クラスをリフレクションでのみインスタンス化できるようにしますか?
フィールドインジェクションを使用すると、リフレクションを使用してオブジェクトをインスタンス化し、これらの特定のインジェクションアノテーションをサポートする依存性インジェクション環境にクラスの compatibility を絞り込むことができます。 Java言語に基づく一部のプラットフォームはリフレクション( [〜#〜] gwt [〜#〜] )さえサポートしていないため、フィールド注入クラスは互換性がありません彼らと。
2番目の問題は パフォーマンス です。コンストラクター呼び出し(直接またはリフレクションによる)は、リフレクションフィールドの割り当てよりも常に高速です。依存関係注入フレームワークは、リフレクション分析を使用して、依存関係ツリーを構築し、リフレクションコンストラクターを作成する必要があります。このプロセスにより、パフォーマンスがさらに低下します。
パフォーマンスは 保守性 に影響します。各テストスイートを何らかの依存関係注入コンテナーで実行する必要がある場合、数千の単体テストのテスト実行には数十分かかることがあります。コードベースのサイズによっては、これが問題になる場合があります。
これはすべて多くの新しい質問を引き起こします:
一般に、プロジェクトがより大きく、より重要であるほど、これらの要因はより重要になります。また、品質に関しては、互換性、テスト容易性、保守性に関してコードを高く維持したいと考えています。哲学的な観点から見ると、フィールドインジェクションはJavaの主要なパラダイムである オブジェクト指向プログラミング の4つの基本の1つである カプセル化 を壊します。
フィールド注入に対する多くの議論。
フィールド注入は、私から明確な「いいえ」の投票を取得します。
ロバート・ハーヴェイのように、それは私の好みには少しオートマジックです。私は暗黙的なコードよりも明示的なコードを好み、コードを理解し、推論することを困難にするため、明確な利点を提供する場合にのみ間接参照を許容します。
MaciejChałapukのように、クラスをインスタンス化するためにリフレクションやDI/IoCフレームワークが必要になるのは好きではありません。アムステルダムからパリに行くためにローマに車で行くようなものです。
DIとそれがもたらすすべての利点が大好きです。クラスをインスタンス化するためにフレームワークが必要かどうかわからない。特に、IoCコンテナや他のDIフレームワークがテストコードに与える影響はありません。
私のテストは非常に単純であることが好きです。 IoCコンテナーまたは他のDIフレームワークをセットアップして、テスト対象のクラスをセットアップするという間接的な作業を行う必要がありません。ぎこちなく感じます。
そしてそれは私のテストをフレームワークのグローバルな状態に依存させる。つまり、クラスXのインスタンスを異なる依存関係でセットアップする必要がある2つのテストを並行して実行することはできなくなりました。
少なくともコンストラクターとセッターインジェクションを使用すると、テストでフレームワークやリフレクションを使用しないという選択肢があります。
さて、これは論争の的となっている議論のようです。最初に対処する必要があるのは、フィールド注入がセッター注入と異なることです。 FieldとSetterの注入は同じものだと思っている人たちとはすでに協力しています。
だから、明確にするために:
フィールド注入:
@Autowired
private SomeService someService;
セッター注入:
@Autowired
public void setSomeService(SomeService someService) {
this.someService = someService;
}
しかし、私にとっては、どちらも同じ懸念を共有しています。
そして、私のお気に入り、コンストラクター注入:
@AutoWired
public MyService(SomeService someService) {
this.someService = someService;
}
コンストラクター注入がセッター/フィールド注入よりも優れていると信じる理由は次のとおりです(私のポイントを示すためにいくつかのSpringリンクを引用します)。
@AutoWired
コード間で広がり、コードはフレームワークにあまり依存しません。プロジェクトのDIフレームワークを単純に変更する可能性は、それを正当化しないことを知っています(誰がそうするのでしょうか?)。しかし、これは私にとって、より良いコードを示しています(EJBとルックアップを使用して 難しい方法 でこれを学びました)。また、Springを使用すると、@AutoWired
コンストラクター注入を使用した注釈。詳細については、Spring Blogの この古い(ただし関連性のある)記事 をお勧めします。これにより、セッターインジェクションを使用する理由と、コンストラクターインジェクションの使用が推奨されます。
セッターインジェクションは、予想以上に頻繁に使用されます。これは、Springのようなフレームワークは一般に、コンストラクターインジェクションよりもセッターインジェクションで構成する方がはるかに適しているという事実です。
そして、コンストラクタがアプリケーションコードにより適していると彼らが信じる理由についてのこのメモ:
コンストラクターインジェクションは、フレームワークコードよりもアプリケーションコードの方がはるかに使いやすいと思います。アプリケーションコードでは、本質的に、構成する必要があるオプションの値の必要性が少なくなります。
Spring team で説明されているように、コンストラクターの利点が本当にわからない場合は、セッター(フィールドではなく)をコンストラクターの注入と混合するという考えがオプションかもしれません。
コンストラクターベースとセッターベースの両方のDIを混在させることができるため、必須の依存関係にはコンストラクターの引数を使用し、オプションの依存関係にはセッターを使用することをお勧めします。
ただし、フィールドインジェクションはおそらく3つのうちの1つ 避けるべき であることに注意してください。