自動テストを学習する(そして愛する)ので、ほとんどすべてのプロジェクトで依存性注入パターンを使用していることに気付きました。自動テストを使用する場合、このパターンを使用することは常に適切ですか?依存性注入の使用を避けるべき状況はありますか?
自動テストを学習する(そして愛する)ので、ほとんどすべてのプロジェクトで依存性注入パターンを使用していることに気付きました。自動テストを使用する場合、このパターンを使用することは常に適切ですか?依存性注入の使用を避けるべき状況はありますか?
回答:
基本的に、依存性注入は、オブジェクトの性質に関する(通常は常にではありませんが)いくつかの仮定を行います。それらが間違っている場合、DIは最良の解決策ではない可能性があります。
まず、最も基本的に、DIはオブジェクト実装の密結合は常に悪いと仮定します。これは、依存関係反転の原則の本質です。「依存関係は、結石に対しては決して行われるべきではなく、抽象化に対してのみ」。
これは、具体的な実装への変更に基づいて、変更する依存オブジェクトを閉じます。ConsoleWriterに依存するクラスは、出力を代わりにファイルに送信する必要がある場合に変更する必要がありますが、クラスがWrite()メソッドを公開するIWriterのみに依存している場合、現在使用されているConsoleWriterをFileWriterおよび従属クラスはその違いを知りません(リスホフ代替原理)。
ただし、すべての種類の変更に対して設計を閉じることはできません。IWriterインターフェイス自体のデザインが変更され、Write()にパラメーターを追加するには、実装オブジェクト/メソッドとその使用に加えて、追加のコードオブジェクト(IWriterインターフェイス)を変更する必要があります。実際のインターフェイスの変更がそのインターフェイスの実装の変更よりも可能性が高い場合、疎結合(および疎結合のDI結合)が解決するよりも多くの問題を引き起こす可能性があります。
第二に、必然的に、DIは依存クラスが依存関係を作成するのに適した場所ではないと想定します。これは単一責任の原則に当てはまります。依存関係を作成し、それを使用するコードがある場合、依存クラスを変更(使用法または実装の変更)しなければならない2つの理由があり、SRPに違反します。
ただし、DIの間接化のレイヤーを追加することは、存在しない問題の解決策になる可能性があります。ロジックを依存関係にカプセル化することが論理的であるが、そのロジックが依存関係の唯一のそのような実装である場合、依存関係(注入、サービスの場所、ファクトリー)の疎結合の解決をコーディングするのは、それよりも苦痛ですただ使用してnew
、それを忘れてください。
最後に、DIはその性質上、すべての依存関係とその実装に関する知識を集中化します。これにより、インジェクションを実行するアセンブリに必要な参照の数が増えますが、ほとんどの場合、実際の依存クラスのアセンブリに必要な参照の数は減りません。
SOMETHING、SOMEWHEREは、「ドットを接続」してその依存関係を満たすために、依存関係、依存関係インターフェース、および依存関係の実装に関する知識が必要です。DIは、IoCコンテナー、または依存関係をハイドレートする(またはファクトリーメソッドを提供する)メインフォームやコントローラーなどの「メイン」オブジェクトを作成するコードのいずれかに、そのすべての知識を非常に高いレベルで配置する傾向があります。これにより、アプリの高レベルで多くの必然的に密結合されたコードと多くのアセンブリ参照を配置できます。実際の依存クラス(非常に基本的な観点からは、この知識を得るのに最適な場所、それが使用されている場所)。
また、通常、コードの下位から上記の参照を削除しません。依存関係は、次の3つの場所のいずれかにある依存関係のインターフェイスを含むライブラリを引き続き参照する必要があります。
このすべてが、再び存在しないかもしれない場所の問題を解決するために。
依存性注入フレームワークの外では、依存性注入(コンストラクター注入またはセッター注入による)はほぼゼロサムゲームです:オブジェクトAと依存関係Bの間のカップリングを減らしますが、Aのインスタンスを必要とするオブジェクトは今すぐ必要ですオブジェクトBも作成します。
AとBの間の結合をわずかに減らしましたが、Aの依存関係にも結合することで、Aのカプセル化を減らし、AとAのインスタンスを構築する必要があるクラス間の結合を増やしました。
そのため、依存性注入(フレームワークなし)は、有益であると同時にほぼ有害です。
ただし、多くの場合、追加のコストは簡単に正当化できます。クライアントコードがオブジェクト自体よりも依存関係の構築方法を知っている場合、依存関係の注入は実際に結合を低減します。たとえば、スキャナーは、入力を解析するための入力ストリームを取得または構築する方法、またはクライアントコードが入力を解析するソースをあまり知りません。そのため、入力ストリームのコンストラクター注入は明らかな解決策です。
モックの依存関係を使用できるようにするための別の理由は、テストです。これは、依存関係の挿入を許可するテストのみに使用される追加のコンストラクターを追加することを意味する必要があります:代わりにコンストラクターを変更して依存関係を常に挿入する必要がある場合は、突然、依存関係の依存関係を構築して直接的な依存関係があり、作業を完了できません。
これは役立つこともありますが、各依存関係について明確に自問する必要があります。テストの利点はコストに見合うものであり、テスト中にこの依存関係を真似したいのでしょうか?
依存関係注入フレームワークが追加され、依存関係の構築がクライアントコードではなくフレームワークに委任されると、費用/便益分析が大きく変わります。
依存関係注入フレームワークでは、トレードオフは少し異なります。依存関係を注入することで失うのは、どの実装に依存しているかを簡単に知ることができ、依存している依存関係を決定する責任を自動化された解決プロセスに移すことです(たとえば、@ Inject'ed Foo 、@ Provides Foo、および注入された依存関係が利用可能なもの)、または各リソースに使用するプロバイダーを規定する高レベルの構成ファイル、または2つのハイブリッドのいずれか(たとえば、必要に応じて構成ファイルを使用してオーバーライドできる依存関係の自動解決プロセスです。
コンストラクター注入の場合と同様に、そうすることの利点は、そうすることのコストに非常に似ていると思います。依存しているデータを誰が提供しているかを知る必要はありません。プロバイダーの場合、プロバイダーをチェックする優先順序を知る必要はありません。データを必要とするすべての場所がすべての潜在的なプロバイダーのチェックを行うことなどを確認してください。プラットフォーム。
個人的にはDIフレームワークの経験はあまりありませんが、必要なデータやサービスの適切なプロバイダーを見つけるという頭痛が頭痛よりも高い場合、コストよりもメリットが大きいと思います。何かが失敗したとき、どのコードが悪いデータを提供したかをすぐにローカルで知らず、それがコードのその後の失敗を引き起こした。
場合によっては、DIフレームワークがシーンに登場したときに依存関係を曖昧にする他のパターン(サービスロケーターなど)が既に採用されており(おそらく価値があることも証明されています)、DIフレームワークは、定型コードの削減、または実際にどのプロバイダーが使用されているかを判断する必要が生じたときに、プロバイダーの依存関係を曖昧にする可能性を減らします。
データベースエンティティを作成する場合は、代わりにコントローラーに注入するファクトリクラスを用意する必要があります。
intやlongなどのプリミティブオブジェクトを作成する必要がある場合。また、日付、GUIDなどのほとんどの標準ライブラリオブジェクトを「手作業で」作成する必要があります。
構成文字列を挿入する場合は、いくつかの構成オブジェクトを挿入することをお勧めします(一般に、単純な型を意味のあるオブジェクトにラップすることをお勧めします:int temperatureInCelsiusDegrees-> CelciusDeegree temperature)
また、依存関係注入の代替手段としてサービスロケーターを使用しないでください。これはアンチパターンです。詳細:http : //blog.ploeh.dk/2010/02/03/ServiceLocatorIsAnAntiPattern.aspx
プロジェクトの保守とテストを可能にすることで何も得られないとき。
真剣に、私は一般的にIoCとDIが大好きで、98%の時間でそのパターンを必ず使用すると言います。ロジックを実装から分離するため、異なるチームメンバーや異なるプロジェクトが何度もコードを再利用できるマルチユーザー環境では特に重要です。別のプロジェクトが同じロギングフレームワークを使用することを保証するものではないため、ロギングフレームワークは、ロギングフレームワークにプラグインするだけでなく、クラスに注入されたILogインターフェースはメンテナンスの数千倍も維持できます。 1つです!)。
ただし、適用可能なパターンではない場合があります。たとえば、オーバーライドできない初期化子(WebMethods、私はあなたを見ていますが、プログラムクラスのMain()メソッドは別の例です)によって静的コンテキストに実装される機能エントリポイントは、初期化時に依存関係を注入することはできません時間。また、プロトタイプ、または捨て去られた調査コードも悪い候補であると言えます。1週間以内にコードの大部分を破棄することが確実な場合、DIの利点はほぼ中長期の利点(テスト容易性と保守性)です。依存関係、コードを動作させるために通常依存関係のテストと分離に費やす時間を費やすだけです。
全体として、どの方法論やパターンにも実用的なアプローチを取ることは賢明です。100%の時間は適用できないからです。
注意すべきことの1つは、自動化されたテストに関するコメントです。この定義は、自動化された機能テストです。たとえば、Webコンテキストの場合は、スクリプト化されたセレンテストです。これらは一般に完全にブラックボックスのテストであり、コードの内部動作を知る必要はありません。ユニットテストまたは統合テストについて言及している場合、DIパターンは、そのようなホワイトボックステストに大きく依存しているプロジェクトにほとんど常に適用可能であると言えます。 DBが存在しなくてもDBにアクセスするメソッド。
他の答えは技術的な側面に焦点を当てていますが、実用的な側面を追加したいと思います。
依存性注入の導入が成功するためには、長年にわたって、いくつかの実際的な要件を満たす必要があるという結論に達しました。
それを導入する理由があるはずです。
これは当たり前のように聞こえますが、コードがデータベースから物事を取得し、ロジックなしでそれを返す場合、DIコンテナを追加すると物事はより複雑になり、実質的な利益はありません。ここでは、統合テストがより重要になります。
チームは訓練を受け、乗船する必要があります。
チームの大半が参加しており、DIを理解していない限り、コントロールコンテナーの反転を追加することが物事を行う別の方法になり、コードベースがさらに複雑になります。
チームの新しいメンバーによってDIが導入された場合、彼らはそれを理解し、それが好きで、彼らが良いことを示したいだけであり、チームが積極的に関与していないため、実際に品質が低下するという本当のリスクがありますコード。
テストする必要があります
一般的にデカップリングは良いことですが、DIは依存関係の解決をコンパイル時から実行時に移行できます。うまくテストしないと、これは実際には非常に危険です。実行時解決の失敗は、追跡と解決に費用がかかる場合があります。
(テストから明らかなことですが、多くのチームは、DIが必要とする程度までテストしていません。)
これは完全な答えではなく、別のポイントです。
一度起動するアプリケーションがあり、長時間実行される場合(Webアプリなど)、DIが良い場合があります。
何度も起動し、より短い時間(モバイルアプリのように)実行するアプリケーションがある場合、おそらくコンテナーは必要ありません。
基本的なOOP原則を使用してみてください:継承を使用して、プライベート/内部/保護されたメンバー/タイプを使用して外部から保護する必要のある共通機能を抽出し、カプセル化(非表示)します。https://www.typemock.com/やhttps://www.telerik.com/products/mocking.aspxなど、強力なテストフレームワークを使用して、テスト専用のコードを挿入します。
次に、DIで書き直して、通常DIで表示されるコードを比較します。
私が見たことから、ほとんどの場合、コードの品質はDIによって低下していると思います。
ただし、クラス宣言で「パブリック」アクセス修飾子のみ、および/またはメンバーのパブリック/プライベート修飾子を使用する場合、および/または高価なテストツールを購入する選択肢がなく、同時にできるユニットテストが必要な場合統合テストに置き換わる、および/または注入しようとしているクラスのインターフェースがすでにある場合、DIは良い選択です!
psおそらく、この投稿には多くのマイナス点があります。現代の開発者のほとんどは、内部キーワードを使用する方法と理由、コンポーネントのカップリングを減らす方法、そして最後にそれを減らす理由を理解していないからですコーディングして比較しよう
依存性注入の代替手段は、サービスロケーターの使用です。Service Locatorは、特にDIフレームワークを使用していない場合、理解、デバッグが容易であり、オブジェクトの構築をより簡単にします。サービスロケーターは、外部の静的な依存関係、たとえばデータアクセスレイヤーのすべてのオブジェクトに渡す必要があるデータベースなどを管理するための優れたパターンです。
ときレガシーコードをリファクタリング、多くの場合、依存性の注入に比べてサービスロケータにリファクタリングする方が簡単です。インスタンス化をサービスルックアップに置き換え、ユニットテストでサービスを偽造するだけです。
ただし、Service Locator にはいくつかの欠点があります。依存関係はコンストラクターやセッターではなく、クラスの実装に隠されているため、クラスの依存関係を知ることはより困難です。また、同じサービスの異なる実装に依存する2つのオブジェクトを作成することは困難または不可能です。
TLDR:クラスに静的な依存関係がある場合、またはレガシーコードをリファクタリングしている場合、Service LocatorはDIよりも間違いなく優れています。