最近読んだ本マーク・シーマンの記事サービスロケータのアンチパターンについて。
著者は、ServiceLocator がアンチパターンである主な理由を 2 つ指摘しています。
API 使用の問題(私はそれで全く問題ありません)
クラスがサービス ロケーターを採用している場合、ほとんどの場合、クラスにはパラメーターのないコンストラクターが 1 つしかないため、その依存関係を確認するのは非常に困難です。ServiceLocator とは対照的に、DI アプローチでは、コンストラクターのパラメーターを介して依存関係を明示的に公開するため、依存関係は IntelliSense で簡単に確認できます。メンテナンスの問題(私には理解できませんが)
次の例を考えてみましょう
クラスがあります'私のタイプ'サービスロケータアプローチを採用しています:
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 アプローチでは問題が発生しませんか? そのクラスをインスタンス化していたすべてのテストを更新する必要があるのではないでしょうか? テストをコンパイル可能にするためだけに、新しいモック依存関係を渡すようにテストを更新します。そして、その更新と時間の浪費から得られるメリットは見当たりません。
私は Service Locator アプローチを擁護しようとしているわけではありません。しかし、この誤解により、私は非常に重要なものを失っているのではないかと考えています。誰か私の疑問を払拭してもらえませんか?
更新(要約):
「Service Locator はアンチパターンか」という私の質問に対する答えは、状況によって異なります。また、ツール リストから Service Locator を削除することは絶対にお勧めしません。レガシー コードを扱い始めるときに、Service Locator が非常に便利になる場合があります。幸運にもプロジェクトの初期段階にいる場合は、Service Locator よりも優れた点がいくつかあるため、DI アプローチの方が適している可能性があります。
新しいプロジェクトで Service Locator を使用しないことにした主な違いは次のとおりです。
- 最も明白かつ重要な点:サービスロケータはクラスの依存関係を隠します
- IoCコンテナを使用している場合は、起動時にすべてのコンストラクタをスキャンしてすべての依存関係を検証し、不足しているマッピング(または間違った構成)に関する即時フィードバックを提供します。これは、IoCコンテナをサービスロケータとして使用している場合、不可能です。
詳細については、以下に記載されている優れた回答をお読みください。
ベストアンサー1
パターンが適合しない状況があるという理由だけで、パターンをアンチパターンと定義するのであれば、それは確かにアンチパターンです。しかし、その理屈では、すべてのパターンもアンチパターンになります。
代わりに、パターンの有効な使用法があるかどうかを確認する必要があります。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 アプローチでは問題が発生しませんか?
いいえ。静的サービス ロケータへの依存関係はありません。静的依存関係を使用して並列テストを実行しようとしましたか? 楽しくありません。