最近、私は Mark Seemannの記事 Service Locatorアンチパターンについて読みました。
著者は、ServiceLocatorがアンチパターンである2つの主な理由を指摘しています。
APIの使用に関する問題(これで問題ありません)
クラスがサービスロケーターを使用する場合、ほとんどの場合、クラスにはPARAMETERLESSコンストラクターが1つしかないため、その依存関係を確認するのは非常に困難です。 ServiceLocatorとは対照的に、DIアプローチは、コンストラクターのパラメーターを介して依存関係を明示的に公開するため、IntelliSenseで依存関係を簡単に確認できます。
メンテナンスの問題(私を困惑させます)
次の例を検討してください
サービスロケーターアプローチを採用するクラス 'MyType'があります。
public class MyType
{
public void MyMethod()
{
var dep1 = Locator.Resolve<IDep1>();
dep1.DoSomething();
}
}
次に、クラス「MyType」に別の依存関係を追加します。
public class MyType
{
public void MyMethod()
{
var dep1 = Locator.Resolve<IDep1>();
dep1.DoSomething();
// new dependency
var dep2 = Locator.Resolve<IDep2>();
dep2.DoSomething();
}
}
そして、ここから私の誤解が始まります。著者は言う:
重大な変更を導入しているかどうかを判断するのは非常に難しくなります。 Service Locatorが使用されているアプリケーション全体を理解する必要がありますが、コンパイラはあなたを助けません。
しかし、ちょっと待ってください。DIアプローチを使用している場合は、コンストラクターに別のパラメーターを持つ依存関係を導入します(コンストラクター注入の場合)。そして、問題はまだそこにあります。 ServiceLocatorのセットアップを忘れる場合は、IoCコンテナーに新しいマッピングを追加するのを忘れる可能性があり、DIアプローチでは同じ実行時の問題が発生します。
また、著者はユニットテストの難しさについて言及しました。しかし、DIアプローチに問題はありませんか?そのクラスをインスタンス化していたすべてのテストを更新する必要はありませんか?テストをコンパイル可能にするために、新しいモック化された依存関係を渡すように更新します。そして、私はその更新と時間の消費からの利益を見ていません。
私はサービスロケーターアプローチを擁護しようとはしていません。しかし、この誤解により、私は何か非常に重要なものを失っていると考えさせられます。誰かが私の疑問を払拭できますか?
UPDATE(SUMMARY):
「サービスロケーターはアンチパターンですか」という私の質問に対する答えは、状況によって異なります。そして、私はあなたのツールリストからそれを消すことは絶対に勧めません。レガシーコードの処理を開始するときに非常に便利になる場合があります。プロジェクトの最初の段階にいる幸運な人には、Service Locatorに比べていくつかの利点があるため、DIアプローチの方が適しているかもしれません。
そして、私の新しいプロジェクトにService Locatorを使用しないことを確信させた主な違いは次のとおりです。
詳細については、下記の優れた回答をご覧ください。
当てはまらない状況があるという理由だけでパターンをアンチパターンとして定義する場合、はい、それはアンチパターンです。しかし、その理由から、すべてのパターンはアンチパターンにもなります。
代わりに、パターンの有効な使用法があるかどうかを確認する必要があり、Service Locatorにはいくつかの使用例があります。しかし、あなたが与えた例を見てみましょう。
public class MyType
{
public void MyMethod()
{
var dep1 = Locator.Resolve<IDep1>();
dep1.DoSomething();
// new dependency
var dep2 = Locator.Resolve<IDep2>();
dep2.DoSomething();
}
}
そのクラスのメンテナンスの悪夢は、依存関係が隠されていることです。そのクラスを作成して使用する場合:
var myType = new MyType();
myType.MyMethod();
サービスロケーションを使用して非表示にした場合、依存関係があることを理解できません。今、代わりに依存性注入を使用する場合:
public class MyType
{
public MyType(IDep1 dep1, IDep2 dep2)
{
}
public void MyMethod()
{
dep1.DoSomething();
// new dependency
dep2.DoSomething();
}
}
依存関係を直接見つけることができ、それらを満たす前にクラスを使用することはできません。
通常の基幹業務アプリケーションでは、まさにその理由でサービスロケーションの使用を避ける必要があります。他のオプションがない場合に使用するパターンでなければなりません。
番号。
たとえば、コントロールコンテナの反転は、サービスの場所がないと機能しません。彼らがサービスを内部的に解決する方法です。
ただし、ASP.NET MVCとWebApiがより良い例です。コントローラで依存性注入を可能にするものは何だと思いますか?そうです-サービスの場所。
しかし、ちょっと待ってください。DIアプローチを使用している場合は、コンストラクターに別のパラメーターを持つ依存関係を導入します(コンストラクター注入の場合)。そして、問題はまだそこにあります。
さらに2つの深刻な問題があります。
コンテナを使用したコンストラクター注入により、無料で取得できます。
ServiceLocatorのセットアップを忘れる場合は、IoCコンテナーに新しいマッピングを追加するのを忘れる可能性があり、DIアプローチでは同じ実行時の問題が発生します。
それは本当だ。ただし、コンストラクター注入では、クラス全体をスキャンして、欠落している依存関係を特定する必要はありません。
また、いくつかの優れたコンテナは、起動時にすべての依存関係を検証します(すべてのコンストラクターをスキャンします)。したがって、これらのコンテナを使用すると、後の一時的な時点ではなく、実行時エラーが直接取得されます。
また、著者はユニットテストの難しさについて言及しました。しかし、DIアプローチに問題はありませんか?
いいえ。静的なサービスロケーターに依存していないため。静的依存関係を処理する並列テストを取得しようとしましたか?面白くない。
また、レガシーコードをリファクタリングする場合、Service Locatorパターンはアンチパターンであるだけでなく、実用的な必要性でもあることを指摘しておきます。数百万行を超えるコードの魔法の杖を振る人はいません。突然、すべてのコードがDI対応になります。したがって、既存のコードベースにDIを導入したい場合、DIサービスになるように物事を変更することがよくあり、これらのサービスを参照するコードはDIサービスではないことがよくあります。したがって、これらのサービスは、DIを使用するように変換されたサービスのインスタンスを取得するために、Service Locatorを使用する必要があります。
したがって、DIの概念の使用を開始するために大規模なレガシーアプリケーションをリファクタリングする場合、Service Locatorはアンチパターンではなく、DIの概念をコードベースに徐々に適用する唯一の方法であると言えます。
テストの観点から見ると、Service Locatorは悪いです。 Misko HeveryのGoogle Tech Talkのニースの説明とコード例をご覧ください http://youtu.be/RlfLCWKxHJ 8分45秒から開始。私は彼の例えが好きでした。25ドルが必要な場合は、お金がどこから取られるかを財布に渡すのではなく、直接お金を要求してください。彼はまた、Service Locatorを、必要な針を持ち、それを取得する方法を知っている干し草の山と比較します。 Service Locatorを使用するクラスは、そのため再利用が困難です。
メンテナンスの問題(私を困惑させます)
この点でサービスロケーターの使用が悪い理由は2つあります。
プレーンでシンプル:サービスロケーターを含むクラスは、コンストラクターを通じて依存関係を受け入れるクラスよりも再利用するのが難しい.
作成者が
LibraryA
を使用すると決定したServiceLocatorA
からのサービスと、作成者がLibraryB
を使用すると決定したServiceLocatorB
からのサービスを使用する必要がある場合を考えます。 。プロジェクトで2つの異なるサービスロケーターを使用する以外に選択肢はありません。適切なドキュメント、ソースコード、または短縮ダイヤルの作成者がいない場合、いくつの依存関係を構成する必要があるかは推測ゲームです。これらのオプションに失敗すると、デコンパイラーjustを使用して依存関係を把握する必要があります。 2つのまったく異なるサービスロケーターAPIを構成する必要がある場合があり、設計によっては、既存のDIコンテナーを単純にラップすることができない場合があります。 2つのライブラリ間で依存関係の1つのインスタンスを共有することは、まったく不可能な場合があります。サービスロケーターが実際に必要なサービスと同じライブラリに実際に存在しない場合、プロジェクトの複雑さはさらに悪化する可能性があります。追加のライブラリ参照をプロジェクトに暗黙的にドラッグしています。ここで、コンストラクター注入を使用して作成された同じ2つのサービスを検討します。
LibraryA
への参照を追加します。LibraryB
への参照を追加します。 DI構成の依存関係を提供します(Intellisenseを介して必要なものを分析します)。できた.Mark Seemannには StackOverflowの回答がグラフィック形式でこの利点を明確に示しています があり、これは別のライブラリのサービスロケーターを使用するときだけでなく、サービスで外部デフォルトを使用するときにも適用されます。
私の知識はこれを判断するのに十分ではありませんが、一般的に、何かが特定の状況で用途がある場合、それは必ずしもアンチパターンではないことを意味するとは思いません。特に、サードパーティのライブラリを扱う場合、すべての側面を完全に制御することはできず、最終的には最適ではないソリューションを使用することになります。
C#によるアダプティブコードの段落を次に示します。
「残念ながら、サービスロケーターは避けられないアンチパターンである場合があります。一部のアプリケーションタイプ(特にWindows Workflow Foundation)では、インフラストラクチャはコンストラクターインジェクションに適していません。これらの場合、唯一の選択肢はサービスロケーターを使用することですすべての依存関係を注入しないことよりも優れています(アンチ)パターンに対する私のすべての悪意について、手動で依存関係を構築するよりも無限に優れています。結局、デコレータ、アダプタ、および同様の利点。」
-ホール、ゲイリーマクリーン。 C#経由のアダプティブコード:デザインパターンとSOLID原則(開発者リファレンス)(p。309)。Pearson Education。
著者は、「コンパイラはあなたを助けない」と推論します-そしてそれは本当です。クラスを設計するときは、インターフェイスを慎重に選択する必要があります。
明示的なインターフェースを介してクライアントに(依存関係への)サービスへの参照を許可させることにより、
DIには問題/欠点があることは間違いありませんが、言及された利点はそれらをはるかに上回っています... IMO。確かに、DIにはインターフェイス(コンストラクター)に依存関係が導入されていますが、これがまさに必要な依存関係であり、可視性とチェック可能性を高めたいと願っています。