フィールド間の依存性注入は悪い習慣です

翻訳されたフィールド依存性注入と考えられるVojtech Ruzickaによる有害な記事



画像



フィールド依存性注入は、SpringなどのDIフレームワークで非常に一般的な方法です。 ただし、この方法にはいくつかの重大なトレードオフがあるため、より頻繁に回避する必要があります。



実装の種類



依存関係をクラスに注入する主な方法は、コンストラクター、セッター、フィールドの3つです。 コードを各アプローチで実装された同じ依存関係とすばやく比較しましょう。



コンストラクター



private DependencyA dependencyA; private DependencyB dependencyB; private DependencyC dependencyC; @Autowired public DI(DependencyA dependencyA, DependencyB dependencyB, DependencyC dependencyC) { this.dependencyA = dependencyA; this.dependencyB = dependencyB; this.dependencyC = dependencyC; }
      
      





セッター



 private DependencyA dependencyA; private DependencyB dependencyB; private DependencyC dependencyC; @Autowired public void setDependencyA(DependencyA dependencyA) { this.dependencyA = dependencyA; } @Autowired public void setDependencyB(DependencyB dependencyB) { this.dependencyB = dependencyB; } @Autowired public void setDependencyC(DependencyC dependencyC) { this.dependencyC = dependencyC; }
      
      





フィールド



 @Autowired private DependencyA dependencyA; @Autowired private DependencyB dependencyB; @Autowired private DependencyC dependencyC;
      
      





何が悪いの?



ご覧のとおり、フィールドを介した実装オプションは非常に魅力的に見えます。 非常に簡潔で表現力豊かで、テンプレートコードはありません。 コードは簡単にナビゲートして読むことができます。 クラスは単にコア機能に集中することができ、定型的なDIコードで散らかっていません。 フィールドの上に@Autowiredアノテーションを配置するだけです-それだけです。 DIコンテナーが必要な依存関係を確実に提供するためだけに、特別なコンストラクターまたはセッターを作成する必要はありません。 Javaはそれ自体かなり冗長なので、あらゆる機会を利用してコードを短くする価値がありますよね?



単独責任の原則の違反



新しい依存関係の追加は簡単です。 たぶん単純すぎます。 6、10、またはそれ以上の依存関係を追加しても問題ありません。 実装にコンストラクターを使用する場合、特定のポイントの後、コンストラクター引数の数が大きくなりすぎて、何かが間違っていることがすぐに明らかになります。 通常、依存関係が多すぎるということは、クラスの責任範囲が多すぎることを意味しています。 これは、単一の責任と責任の分離の原則に違反する可能性があり( 元:関心の分離 )、クラスをより慎重に研究およびリファクタリングする価値があることを示す良い指標です。 フィールドを介した実装を使用する場合、そのような明示的なアラームインジケータは存在しないため、埋め込まれた依存関係は無限に増大します。



依存関係の非表示



DIコンテナを使用するということは、クラスが依存関係を管理する責任をもはや負わないことを意味します。 それらを受け取る責任はクラスから外部に移され、今では他の誰かがそれらを提供する責任があります:それはDIコンテナまたはテストを通してそれらを手動で提供することができます。 クラスが依存関係の取得を担当しなくなった場合、パブリックインターフェイス(メソッドまたはコンストラクター)を使用してクラスと明示的に対話する必要があります。 したがって、クラスに必要なものと、それがオプションの依存関係(セッター経由)か必須(コンストラクター)かが明らかになります。



DIコンテナーの依存関係



DIフレームワークの重要なアイデアの1つは、マネージクラスが使用される特定のコンテナーに依存しないことです。 つまり、単純なPOJOクラスである必要があります。必要な依存関係をすべて渡すと、インスタンスを個別に作成できます。 したがって、コンテナを起動せずに単体テストで作成し、個別にテストできます(コンテナを使用すると、統合テストになります)。 コンテナタイがない場合、クラスを管理対象または管理対象外として使用するか、別のDIフレームワークに切り替えることもできます。



ただし、フィールドに直接注入する場合、必要なすべての依存関係を持つクラスのインスタンスを直接作成する方法は提供しません。 これは次のことを意味します。





不変性



コンストラクターを使用する方法とは異なり、フィールドによる実装を使用して最終フィールドに依存関係を割り当てることはできません。これにより、オブジェクトが可変になります。



コンストラクターとセッターによる実装



したがって、フィールドを介した注入は良い方法ではないかもしれません。 残っているものは何ですか? セッターとコンストラクター。 どちらを使用すればよいですか?



セッター



オプションの依存関係を注入するには、セッターを使用する必要があります。 クラスは、提供されなくても機能する必要があります。 依存関係は、オブジェクトの作成後いつでも変更できます。 これは、状況に応じて利点になる場合もあれば、そうでない場合もあります。 不変オブジェクトを持つことが望ましい場合があります。 JMXの管理対象MBeanなど、実行時にオブジェクトの構成部分を変更すると便利な場合があります。

Spring 3.xの公式ドキュメント勧告では、コンストラクターよりもセッターの使用を推奨しています。

特にプロパティがオプションの場合、多数のコンストラクター引数が面倒になる可能性があるため、Springチームは主にセッターを介したインジェクションを推奨しています。 また、セッターは、このクラスのオブジェクトを後で再構成または再注入するのに適したものにします。 JMX MBeansを介した管理が代表的な例です



一部の純粋主義者は、コンストラクターベースの注入を好みます。 すべての依存関係を提供するということは、オブジェクトが常に完全に初期化された状態で呼び出し元のコードに戻ることを意味します。 欠点は、オブジェクトの再構成および再注入が少なくなることです


コンストラクター



コンストラクターを介したインジェクションは、依存関係(オブジェクトの正しい機能に必要な依存関係)のバインドに適しています。 それらをコンストラクタに渡すことで、オブジェクトが作成された瞬間から完全に使用できる状態になっていることを確認できます。 コンストラクタで割り当てられたフィールドは最終的なものにすることもできます。これにより、オブジェクトを完全に変更せずに、または少なくとも必要なフィールドを保護できます。



コンストラクターを使用して埋め込みを使用することの結果の1つは、この方法で作成された2つのオブジェクト間を(セッターを使用して埋め込むのではなく)循環させることが不可能になったことです。 循環的な依存関係は避けなければならないため、これは制限よりもプラスになります。これは通常、貧弱なアーキテクチャの兆候です。 したがって、同様の慣行が防止されます。



もう1つの利点は、Springバージョン4.3+を使用すると、特定のDIフレームワークからクラスを完全に分離できることです。 その理由は、Springがシングルユースケースシナリオの暗黙的なコンストラクター注入をサポートするようになったためです。 これは、クラスでDI注釈が不要になったことを意味します。 もちろん、このクラスのSpring設定で明示的にDIを構成することで同じ結果を得ることができます。 簡単になりました。



Spring 4.xに関しては、 ドキュメントからの公式の推奨事項が変更され、セッターを介したインジェクションはコンストラクターよりも優先されなくなりました。

Springチームは、アプリケーションコンポーネントを不変オブジェクトとして実装し、必要な依存関係がnullでないことを保証できるため、コンストラクターを介した注入を主に推奨しています 。 さらに、コンストラクターを通じて実装されたコンポーネントは、常に完全に初期化された状態でクライアントコードに戻ります。 小さなコメントとして、多数のコンストラクター引数は「zipを使用したコード」の記号であり、おそらくクラスの責任が多すぎるため、責任の分離の問題をよりよく解決するために再編成する必要があることを意味します。



セッターインジェクションは主に、クラス内のデフォルト値を割り当てることができるオプションの依存関係に使用する必要があります。 そうでない場合、コードがこれらの依存関係を使用する場合は常に、 null以外のチェックを使用する必要があります。 セッターを介して埋め込みを使用する利点の1つは、クラスオブジェクトを後で再構成および再注入可能にすることです。


おわりに



基本的に、フィールドへの埋め込みは避けてください。 あるいは、セッターまたはコンストラクターを実装に使用する必要があります。 それぞれに、状況に応じた長所と短所があります。 ただし、これらのアプローチは混在する可能性があるため、これは「どちらか」の選択ではなく、セッターとコンストラクターの両方を介して同じクラスの注入を組み合わせることができます。 コンストラクターは、必須の依存関係および不変オブジェクトの必要性により適しています。 セッターは、オプションの依存関係により適しています。



All Articles