この2つの違いとDIコンテナーがサービスロケーターよりも優れている理由を理解する最も簡単な方法は、最初に依存関係の反転を行う理由を考えることだと思います。
依存関係の反転を行い、各クラスが操作のために依存するものを正確に明示するようにします。これは、これにより達成可能な最も緩やかな結合が作成されるためです。結合が緩いほど、テストとリファクタリングが容易になります(そして、コードがクリーンであるため、一般的に将来的には最小限のリファクタリングが必要です)。
次のクラスを見てみましょう。
public class MySpecialStringWriter
{
private readonly IOutputProvider outputProvider;
public MySpecialFormatter(IOutputProvider outputProvider)
{
this.outputProvider = outputProvider;
}
public void OutputString(string source)
{
this.outputProvider.Output("This is the string that was passed: " + source);
}
}
このクラスでは、このクラスを機能させるためにIOutputProviderが必要であり、他には何も必要でないことを明示的に述べています。これは完全にテスト可能であり、単一のインターフェイスに依存しています。このクラスは、別のプロジェクトを含むアプリケーション内の任意の場所に移動でき、必要なのはIOutputProviderインターフェイスへのアクセスだけです。他の開発者がこのクラスに新しいものを追加する場合、2番目の依存関係が必要な場合、コンストラクターで必要なものを明示する必要があります。
サービスロケーターを使用して同じクラスを見てみましょう。
public class MySpecialStringWriter
{
private readonly ServiceLocator serviceLocator;
public MySpecialFormatter(ServiceLocator serviceLocator)
{
this.serviceLocator = serviceLocator;
}
public void OutputString(string source)
{
this.serviceLocator.OutputProvider.Output("This is the string that was passed: " + source);
}
}
これで、依存関係としてサービスロケーターを追加しました。すぐに明らかになる問題は次のとおりです。
- これに関する最初の問題は、同じ結果を達成するためにより多くのコードを必要とすることです。もっとコードが悪いです。それほど多くのコードではありませんが、それでもなお多くあります。
- 2番目の問題は、依存関係が明示的ではなくなったことです。私はまだクラスに何かを注入する必要があります。今を除いて、私が欲しいのは明確ではありません。それは私が要求したもののプロパティに隠されています。クラスを別のアセンブリに移動する場合は、ServiceLocatorとIOutputProviderの両方にアクセスする必要があります。
- 3番目の問題は、クラスにコードを追加するときに依存関係になっていることに気づいていない別の開発者が追加の依存関係を取得できることです。
- 最後に、IOutputProviderだけでなくServiceLocatorとIOutputProviderをモックする必要があるため、このコードをテストするのは難しくなります(ServiceLocatorがインターフェイスである場合でも)。
では、なぜサービスロケーターを静的クラスにしないのでしょうか。見てみましょう:
public class MySpecialStringWriter
{
public void OutputString(string source)
{
ServiceLocator.OutputProvider.Output("This is the string that was passed: " + source);
}
}
これははるかに簡単ですよね?
違う。
IOutputProviderは、世界中の15の異なるデータベースに文字列を書き込む非常に長時間実行されるWebサービスによって実装され、完了までに非常に長い時間がかかるとしましょう。
このクラスをテストしてみましょう。テストには、IOutputProviderの異なる実装が必要です。どのようにテストを書きますか?
それを行うには、静的なServiceLocatorクラスでいくつかの凝った構成を行い、テストによって呼び出されるときにIOutputProviderの異なる実装を使用する必要があります。その文章を書くことさえ苦しかった。それを実装するのは拷問であり、メンテナンスの悪夢です。特にそのクラスが実際にテストしようとしているクラスではない場合、テスト専用にクラスを変更する必要はありません。
したがって、a)無関係なServiceLocatorクラスで目障りなコード変更を引き起こすテストのいずれかが残っています。またはb)テストなし。また、柔軟性の低いソリューションも残されています。
そのため、サービスロケータークラスをコンストラクターに挿入する必要があります。つまり、前述の特定の問題が残っています。サービスロケーターはより多くのコードを必要とし、他の開発者に必要でないことを伝え、他の開発者がより悪いコードを書くことを促し、前進する柔軟性を減らします。
単純にサービスロケーターを使用すると、アプリケーション内の結合が増加し、他の開発者が高度に結合したコードを記述するようになります。