フレームワークを作成する際の依存性注入/ IoCコンテナープラクティス


18

多くのプロジェクトで.NetにさまざまなIoCコンテナー(Castle.Windsor、Autofac、MEFなど)を使用しました。彼らは頻繁に虐待される傾向があり、多くの悪い慣行を助長していることがわかりました。

特にプラットフォーム/フレームワークを提供する場合、IoCコンテナーの使用に関する確立されたプラクティスはありますか?フレームワークライターとしての私の目標は、コードをできるだけシンプルで使いやすいものにすることです。オブジェクトを作成するために、10行または2行だけでなく、1行のコードを作成したいです。

たとえば、私が気づいたいくつかのコードの匂いがして、良い提案がありません:

  1. コンストラクターの多数のパラメーター(> 5)。サービスの作成は複雑になる傾向があります。すべての依存関係はコンストラクタを介して注入されます-コンポーネントがオプションであるということはめったにありませんが(テスト中を除く)。

  2. プライベートクラスと内部クラスの欠如。これは、C#とSilverlightの使用に関する特定の制限かもしれませんが、どのように解決されるのか興味があります。すべてのクラスがパブリックである場合、フレームワークインターフェイスが何であるかを伝えるのは困難です。それは私がおそらく触れるべきではないプライベートな部分にアクセスできるようにします。

  3. オブジェクトのライフサイクルをIoCコンテナーに結合します。多くの場合、オブジェクトの作成に必要な依存関係を手動で構築することは困難です。オブジェクトのライフサイクルは、IoCフレームワークによって頻繁に管理されます。ほとんどのクラスがシングルトンとして登録されているプロジェクトを見てきました。明示的な制御が得られず、内部の管理も強制されます(上記の点に関連し、すべてのクラスはパブリックであり、それらを注入する必要があります)。

たとえば、.Netフレームワークには多くの静的メソッドがあります。DateTime.UtcNowなど。多くの場合、これが構築パラメーターとしてラップおよびインジェクトされるのを見てきました。

具体的な実装によっては、コードのテストが難しくなります。依存関係を挿入すると、コードが使いにくくなります-特にクラスに多くのパラメーターがある場合。

テスト可能なインターフェイスと使いやすいインターフェイスの両方を提供するにはどうすればよいですか?ベストプラクティスは何ですか?


1
彼らはどのような悪い習慣を奨励していますか?プライベートクラスについては、適切にカプセル化されていれば、あまり多くのクラスを用意する必要はありません。1つには、テストがはるかに困難です。
アーロンノート

エラー1と2は悪い習慣です。大規模なコンストラクターで、カプセル化を使用して最小限のインターフェイスを確立していませんか?また、私はプライベートおよび内部(テストするために、目に見える内部を使用します)と言いました。特定のIoCコンテナーの使用を強制するフレームワークを使用したことはありません。
デイブヒリアー

2
クラスコンストラクターに多くのパラメーターを必要とする兆候は、それ自体がコードの匂いであり、DIPがこれをより明確にしている可能性はありますか?
dreza

3
フレームワークで特定のIoCコンテナを使用することは、一般的に良い習慣ではありません。使用依存性注入すること 良い練習。違いを知っていますか?
アーロンノート

1
@Aaronaught(バックオブザデッド)。「依存性注入は良い習慣です」を使用するのは間違っています。依存性注入は、適切な場合にのみ使用するツールです。常に依存性注入を使用するのは悪い習慣です。
デイブヒリアー

回答:


27

私が知っている唯一の正当な依存性注入アンチパターンは、Service Locatorパターンです。これは、DIフレームワークを使用する場合のアンチパターンです。

ここで、または他の場所で私が聞いた他のいわゆるDIアンチパターンはすべて、一般的なオブジェクト指向/ソフトウェア設計アンチパターンのもう少し具体的なケースです。例えば:

  • コンストラクターの過剰注入は、単一責任原則に違反しています。コンストラクター引数が多すぎると、依存関係が多すぎます。依存関係が多すぎるということは、クラスがやりすぎていることを示しています。通常、このエラーは、異常に長いクラス名やあいまいな(「マネージャー」)クラス名など、他のコードの匂いと関連しています。静的解析ツールは、過剰な求心性/遠心性結合を簡単に検出できます。

  • 動作とは対照的に、データの注入はポルターガイストのアンチパターンのサブタイプであり、この場合の「ガイスト」はコンテナーです。クラスが現在の日付と時刻を認識する必要がある場合、DateTimeデータであるを挿入しません。代わりに、システムクロックに抽象化を挿入します(SystemWrappersプロジェクトISystemClockにはもっと一般的なものがあると思いますが、通常はmine と呼びます)。これは、DIだけで正しいわけではありません。テスト容易性には絶対に不可欠であるため、実際に待機する必要なく時変関数をテストできます。

  • Singletonとしてすべてのライフサイクルを宣言することは、私にとっては貨物カルトプログラミングの完璧な例であり、それほどではないが、口語的に「object cesspool」と名付けられています。覚えておくよりも多くのシングルトン虐待を見てきましたが、DIに関係するものはほとんどありません。

  • もう1つの一般的なエラーはIOracleRepository、コンテナに登録できるようにするためだけに行われる実装固有のインターフェイスタイプ(などの奇妙な名前)です。これは、それ自体が依存関係反転の原則に違反するものであり(インターフェイスであるからといって、それが本当に抽象的であるという意味ではありません)、多くの場合、インターフェイス分離の原則に違反するインターフェイスの肥大化も含まれています

  • 私が通常目にする最後のエラーは、「オプションの依存関係」で、NerdDinnerで行いました。言い換えれば、そこにある依存性注入を受け取るコンストラクタが、また別の「デフォルト」の実装を使用するコンストラクタが。これはまた、DIPに違反し、開発者が時間とともにデフォルト実装を前提にしたり、デフォルトコンストラクタを使用してインスタンスを新しくしたりするため、LSP違反にもつながる傾向があります。

古いことわざにあるように、任意の言語でFORTRANを書くことができます。依存性注入は、その依存関係の管理を台無しから開発者を防ぐことができます特効薬はありませんが、それはない一般的なエラー/アンチパターンの数を防ぎます:

...等々。

明らかに、UnityやAutoFacなどの特定のIoCコンテナーの実装に依存するフレームワークを設計する必要はありません。つまり、DIPに違反しています。ただし、そのようなことを考えている場合でも、依存関係の注入は汎用の依存関係管理手法であり、IoCコンテナーの概念に結び付けられていないため、既にいくつかの設計エラーを犯している必要があります。

何でも依存ツリーを構築できます。おそらくIoCコンテナーであるか、多数のモックを使用した単体テストであるか、ダミーデータを提供するテストドライバーである可能性があります。あなたのフレームワークは気にするべきではなく、私が見たほとんどのフレームワークは気にしませんが、それでも依存性注入を多用しているので、エンドユーザーの選択したIoCコンテナーに簡単に統合できます。

DIはロケット科学ではありません。外部の依存関係を持たないユーティリティメソッドや、フレームワークの外では目的を持たない可能性があるユーティリティクラスなど、それらを使用する説得力のある理由がある場合newstatic除いて、避けてください(相互運用ラッパーと辞書キーは、この)。

IoCフレームワークの問題の多くは、開発者が最初にIoCフレームワークの使用方法を学習し、IoCモデルに適合するように依存関係と抽象化を処理する方法を実際に変更するときに発生します。多くの場合、高い結合と低い凝集度を伴う古いコーディングスタイル。悪いコードは、DIテクニックを使用するかどうかに関係なく、悪いコードです。


少し質問があります。NuGetで配布されるライブラリの場合、IoCシステムに依存することはできません。これは、ライブラリ自体ではなく、ライブラリを使用するアプリケーションによって実行されるためです。多くの場合、ライブラリのユーザーは実際には内部依存関係をまったく気にしません。内部依存関係を開始するデフォルトのコンストラクター、および単体テストやカスタマイズされた実装のためのより長いコンストラクターを持つことに問題はありますか?また、「新規」を避けるように言います。これは、StringBuilder、DateTime、TimeSpanなどに適用されますか?
エティエンヌチャーランド
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.