コンテナでの依存性注入の使用とサービスロケーターの使用の違いは何ですか?


107

クラス内で依存関係を直接インスタンス化することは悪い習慣と見なされることを理解しています。これは、すべてを密接に結合するため、テストが非常に困難になるため、理にかなっています。

私が出くわしたほとんどすべてのフレームワークは、サービスロケーターの使用よりもコンテナーでの依存性注入を好むようです。どちらも、クラスが依存関係を必要とするときに返されるオブジェクトをプログラマーが指定できるようにすることで、同じことを達成しているようです。

2つの違いは何ですか?なぜ私は一方をもう一方よりも選ぶのでしょうか?


3
これはStackOverflowの上で尋ねた(と回答)されています:stackoverflow.com/questions/8900710/...
Kyralessa

2
私の意見では、開発者が他者を欺いて、自分のサービスロケーターが依存性注入であると考えることは珍しくありません。多くの場合、依存性注入はより高度であると考えられているためです。
ガーマン


1
Service Locatorsでは、一時的なリソースが破壊される可能性があることを知るのが難しいと誰も言及していないことに驚いています。それが主な理由だといつも思っていました。
ブーブー

回答:


126

コンストラクタを介してオブジェクトを受け入れるのではなく、オブジェクト自体が依存関係をリクエストする責任がある場合、いくつかの重要な情報が隠されています。new依存関係をインスタンス化するために使用する非常に密結合した場合よりもわずかに優れています。実際に取得する依存関係を変更できるため、カップリングは減少しますが、それでも揺るがせない依存関係、つまりサービスロケーターがあります。それがすべてが依存しているものになります。

コンストラクター引数を通じて依存関係を提供するコンテナーは、最も明確になります。オブジェクトにはAccountRepository、との両方が必要であることがすぐにわかりますPasswordStrengthEvaluator。サービスロケーターを使用する場合、その情報はすぐにはわかりません。オブジェクトに17個の依存関係があるケースがすぐに表示され、「うーん、それは多くのように思えます。そこで何が起こっているのですか?」サービスロケーターの呼び出しはさまざまなメソッドに分散され、条件付きロジックの背後に隠れることがあります。「神クラス」を作成したことに気付かない場合があります。たぶんそのクラスは、より焦点が絞られた、よりテスト可能な3つの小さなクラスにリファクタリングされる可能性があります。

テストを検討してください。オブジェクトがサービスロケーターを使用して依存関係を取得する場合、テストフレームワークにはサービスロケーターも必要です。テストでは、サービスロケーターを構成して、テスト対象のオブジェクト(おそらくa FakeAccountRepositoryとa VeryForgivingPasswordStrengthEvaluator)に依存関係を提供し、テストを実行します。しかし、それはオブジェクトのコンストラクターで依存関係を指定するよりも多くの作業です。また、テストフレームワークもサービスロケーターに依存するようになります。すべてのテストで構成する必要があるもう1つのことであり、テストの作成の魅力が低下します。

Mark Semanの記事については、「Serivce Locatorはアンチパターンです」を参照してください。.Netの世界にいるなら、彼の本を入手してください。これはとてもいいです。


1
ゲイリーマクリーンホールによるC#を介したアダプティブコードについて考えた質問を読んでください。サービスロケーターが金庫の鍵であるなど、いくつかの良い類似点があります。クラスに渡されると、簡単に見つけられない可能性がある依存関係を自由に作成できます。
デニス

10
IMOをconstructor supplied dependenciesvsに追加する必要service locatorがあることの1つ、前者はコンパイル時に検証でき、後者は実行時のみ検証できることです。
アンドラス

2
さらに、サービスロケーターは、コンポーネントが多くの依存関係を持つことを妨げません。コンストラクターに10個のパラメーターを追加する必要がある場合、コードに何か問題があると感じることができます。ただし、たまたまサービスロケーターに対して静的呼び出しを10回行っただけでは、何らかの問題が発生していることはすぐにはわかりません。私はサービスロケーターを使用して大規模なプロジェクトに取り組んできましたが、これが最大の問題でした。座って、反映し、再設計し、リファクタリングするのではなく、新しいパスを短絡させるのは簡単すぎました。
ペース

1
テストについて- But that's more work than specifying dependencies in the object's constructor.異議を申し上げます。サービスロケーターを使用すると、テストに実際に必要な3つの依存関係のみを指定する必要があります。コンストラクターベースのDIでは、7個が使用されていない場合でも、10個すべてを指定する必要があります。
Vilx-

4
@ Vilx-未使用のコンストラクターパラメーターが複数ある場合、クラスが単一責任の原則に違反している可能性があります。
RB。

79

あなたがを作る工場の労働者だと想像してください。

あなたは靴を組み立てる責任があるので、それを行うには多くのものが必要になります。

  • レザー
  • 巻き尺
  • 接着剤
  • ハンマー
  • はさみ
  • 靴ひも

等々。

工場で働いていて、開始する準備ができています。手順の説明はありますが、資料やツールはまだありません。

サービスロケータは、あなたが必要なものを手に入れることができますフォアマンのようなものです。

あなたは何かが必要になるたびにサービスロケーターに尋ねると、彼らはあなたのためにそれを見つけるために出かけます。Service Locatorは、あなたが何を求めそうか、どのようにそれを見つけるかについて事前に知らされています。

ただし、予期しない何かを要求しないことを望みます。特定のツールや素材についてロケーターに事前に通知されていない場合、ロケーターはそれを入手することができず、ただあなたに敬意を表します。

サービスロケーター

依存性注入(DI)コンテナは、誰もが一日の開始時に必要なすべてで満たされます大きな箱のようなものです。

工場が始動すると、コンポジションルートとして知られるビッグボスがコンテナをつかみ、すべてをラインマネージャーに渡します。

これで、ラインマネージャーは、その日の業務を遂行するために必要なものを手に入れました。彼らは持っているものを取り、必要なものを部下に渡します。

このプロセスは継続され、依存関係が生産ラインに流れ込みます。最終的には、Foreman用の材料とツールのコンテナが表示されます。

あなたのフォアマンは、あなたが必要とするものをあなたや他の労働者に正確に配布します。

基本的に、仕事に出たらすぐに、必要なものはすべてあなたを待っている箱の中にあります。それらを取得する方法について何も知る必要はありませんでした。

依存性注入コンテナ


45
これは、これら2つのことのそれぞれの優れた説明であり、非常に見栄えの良い図でもあります。しかし、これは「なぜ一方を他方より選択するのか」という質問には答えませんか?
マチューM.

21
「ただし、予期しないことを求めないことを望みます。特定のツールまたはマテリアルについてロケーターに事前に通知されていない場合」しかし、DIコンテナーについても同様です。
ケネスK.

5
@FrankHopkins DIコンテナーへのインターフェース/クラスの登録を省略すると、コードはコンパイルされ、実行時に即座に失敗します。
ケネスK.

3
どちらも、設定方法によっては、同様に失敗する可能性があります。しかし、DIコンテナを使用すると、クラスXが実際にその1つの依存関係を必要とする場合よりも、クラスXが構築される場合の方が早く問題にぶつかる可能性が高くなります。問題に遭遇し、それを見つけて解決する方が簡単なので、間違いなく優れています。答えは、この問題はサービスロケーターのみに存在することを示唆しているというケネスに同意しますが、これは間違いです。
GolezTrol

3
すばらしい答えですが、私はそれがもう一つのことを見逃していると思います。つまり、どの段階でもフォアマンにゾウを頼み、彼ゾウを入手する方法を知っている場合、彼はあなたのために質問をすることはありません。そして、フロアで問題をデバッグしようとしている人(開発者なら)は、このデスクでwtfがここでやっているゾウだと不思議に思うでしょう。一方、DIでは、ワーカークラスは、ジョブを開始する前に必要なすべての依存関係を事前に宣言するため、推論容易になります。
スティーブンバーン

10

ウェブを精査するときに見つけたいくつかの余分なポイント:

  • コンストラクターに依存関係を挿入すると、クラスに必要なものを理解しやすくなります。現代のIDEは、コンストラクターが受け入れる引数とその型を示唆します。サービスロケーターを使用する場合、どの依存関係が必要かを知る前にクラスを読む必要があります。
  • 依存性注入は、サービスロケーターよりも「聞かないで」という原則に準拠しているようです。依存関係が特定のタイプであることを義務付けることにより、どの依存関係が必要かを「伝える」ことができます。必要な依存関係を渡さずにクラスをインスタンス化することは不可能です。サービスロケーターを使用すると、サービスを「求め」ます。サービスロケーターが正しく構成されていない場合、必要なものが得られない場合があります。

4

私はこのパーティーに遅れていますが、抵抗することはできません。

コンテナでの依存性注入の使用とサービスロケーターの使用の違いは何ですか?

時にはまったくありません。違いを生むのは、何について何を知っているかです。

依存関係を探しているクライアントがコンテナについて知っているときに、サービスロケーターを使用していることを知っています。コンテナを介して依存関係を取得する場合でも、依存関係を見つける方法を知っているクライアントがサービスロケーターパターンです。

これは、サービスロケーターを避けたい場合、コンテナーを使用できないことを意味しますか?いいえ。クライアントがコンテナを認識しないようにする必要があります。主な違いは、コンテナを使用する場所です。

Clientニーズを言うことができますDependency。コンテナにはがありDependencyます。

class Client { 
    Client() { 
        BeanFactory beanfactory = new ClassPathXmlApplicationContext("Beans.xml");
        this.dependency = (Dependency) beanfactory.getBean("dependency");        
    }
    Dependency dependency;
}

Client見つける方法を知っているので、サービスロケーターパターンをたどっただけDependencyです。確かにそれはハードコードされてClassPathXmlApplicationContextいますが、をClient呼び出すためにまだサービスロケーターがあることを注入してもbeanfactory.getBean()

サービスロケータを回避するために、このコンテナを放棄する必要はありません。あなただけの外に移動する必要がClientそうClientそれについて知りません。

class EntryPoint { 
    public static void main(String[] args) {
        BeanFactory beanfactory = new ClassPathXmlApplicationContext("Beans.xml");
        Client client = (Client) beanfactory.getBean("client");

        client.start();
    }
}

<?xml version="1.0" encoding="UTF-8"?>
 <beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://www.springframework.org/schema/beans
  http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">

    <bean id="dependency" class="Dependency">
    </bean>

    <bean id="client" class="Client">
        <constructor-arg value="dependency" />        
    </bean>
</beans>

Clientコンテナが存在することを今はまったく知らないことに注意してください。

class Client { 
    Client(Dependency dependency) { 

        this.dependency = dependency;        
    }
    Dependency dependency;
}

コンテナをすべてのクライアントから移動し、mainに固定して、すべての長寿命オブジェクトのオブジェクトグラフを構築できます。それらのオブジェクトの1つを選択してそのメソッドを呼び出し、グラフ全体のティックを開始します。

これにより、すべての静的な構造がコンテナXMLに移動しますが、すべてのクライアントは依存関係を見つける方法をまったく知らないままです。

しかし、mainは依存関係を見つける方法をまだ知っています!はい、そうです。しかし、その知識を周囲に広めないことで、サービスロケーターの中心的な問題を回避しました。コンテナを使用する決定は1か所で行われ、何百ものクライアントを書き直すことなく変更できます。


1

この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)テストなし。また、柔軟性の低いソリューションも残されています。

そのため、サービスロケータークラスコンストラクターに挿入する必要があります。つまり、前述の特定の問題が残っています。サービスロケーターはより多くのコードを必要とし、他の開発者に必要でないことを伝え、他の開発者がより悪いコードを書くことを促し、前進する柔軟性を減らします。

単純にサービスロケーターを使用する、アプリケーション内の結合が増加し、他の開発者が高度に結合したコードを記述するようになります


サービスロケーター(SL)と依存性注入(DI)は、どちらも同じ問題を同じ方法で解決します。依存関係をDIに「伝える」かSLに「求める」場合の唯一の違い。
マシューホワイトニング

@MatthewWhited主な違いは、サービスロケーターで取る暗黙的な依存関係の数です。そして、それはコードの長期的な保守性と安定性に大きな違いをもたらします。
スティーブン

そうではありません。どちらの方法でも同じ数の依存関係があります。
マシューホワイトニング

最初ははいですが、依存関係を取得していることに気付かずに、サービスロケーターで依存関係を取得できます。そもそも依存関係の逆転を行う理由の大部分を打ち負かしています。1つのクラスはプロパティAとBに依存し、もう1つのクラスはBとCに依存します。突然、Service Locatorは神のクラスになります。DIコンテナーはそうではなく、2つのクラスはそれぞれAとBおよびBとCにのみ適切に依存しています。これにより、リファクタリングが100倍簡単になります。サービスロケーターは、DIと同じように見えますが、そうではないという点で潜伏しています。
スティーブン
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.