web-dev-qa-db-ja.com

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

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

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

2つの違いは何ですか?なぜ私は他のものを選ぶのですか?

112
tom6025222

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

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

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

Mark Seemanの記事については、「Serivce Locator is a Anti-Pattern」を参照してください。あなたが.Netの世界にいるなら、彼の本を手に入れてください。これはとてもいいです。

126
Carl Raymond

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

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

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

等々。

あなたは工場で働いていて、始める準備ができています。続行方法の説明のリストはありますが、まだ資料やツールがありません。

Service Locatorは、必要なものを取得するのに役立つフォアマンのようなものです。

何かが必要になるたびにService Locatorに尋ねると、Service Locatorが探しに行きます。 Service Locatorには、何を要求する可能性が高く、どのように見つけるかについて事前に通知されています。

ただし、予期しないことを求めないようにしてください。ロケーターが特定のツールまたは材料について事前に通知されていない場合、ロケーターはそれを入手することができず、ただ肩をすくめます。

service locator

Dependency Injection(DI)Containerは、1日の始めに誰もが必要とするすべてのもので満たされる大きな箱のようなものです。

ファクトリーが起動すると、Composition Rootと呼ばれるビッグボスがコンテナーを取得し、すべてをに渡しますラインマネージャー

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

このプロセスは継続し、依存関係は生産ラインを流れていきます。最終的には、材料とツールのコンテナがフォアマンに表示されます。

あなたの職長は今、あなたや他の労働者に必要なものを正確に配布します。

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

dependency injection container

79
Rowan Freeman

私がウェブを精査したときに見つけたいくつかの追加のポイント:

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

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

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

まったくない場合もあります。違いは何が何を知っているかです。

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

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

ClientにはDependencyが必要だとしましょう。コンテナにはDependencyがあります。

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

ClientDependencyを見つける方法を知っているため、サービスロケーターパターンをたどりました。確かに、ハードコードされたClassPathXmlApplicationContextを使用しますが、Clientbeanfactory.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;
}
_

コンテナをすべてのクライアントの外に移動し、メインに貼り付けて、長持ちするすべてのオブジェクトのオブジェクトグラフを作成します。それらのオブジェクトの1つを選択して抽出し、その上でメソッドを呼び出すと、グラフ全体が動き始めます。

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

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

5
candied_orange

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番目の問題は、追加の依存関係が、クラスにコードを追加するときに気付かないが別の開発者が取得できることです。
  • 最後に、このコードはテストが難しいです(ServiceLocatorがインターフェイスであっても)。

では、なぜサービスロケータを静的クラスにしないのでしょうか。見てみましょう:

public class MySpecialStringWriter
{
  public void OutputString(string source)
  {
    ServiceLocator.OutputProvider.Output("This is the string that was passed: " + source);
  }
}

これはもっと簡単ですよね?

間違っています

IOutputProviderは、世界中の15の異なるデータベースに文字列を書き込み、完了するまでに非常に長い時間がかかる、非常に長時間実行されるWebサービスによって実装されているとしましょう。

このクラスをテストしてみましょう。テストには、IOutputProviderの異なる実装が必要です。テストをどのように記述しますか?

それを行うには、テストによって呼び出されているときにIOutputProviderの異なる実装を使用するために、静的なServiceLocatorクラスでいくつかの凝った構成を行う必要があります。その文章を書くことさえ苦痛でした。それを実装するのは苦痛で、maintenance nightmareになります。特にそのクラスが実際にテストしようとしているクラスでない場合は特に、テスト専用にクラスを変更する必要はありません。

したがって、次のいずれかが残ります。a)無関係のServiceLocatorクラスでコードを目障りなコードに変更するテスト。またはb)テストがまったくない。また、柔軟性の低いソリューションも残ります。

したがって、サービスロケータクラスは、コンストラクタに注入する必要があります。これは、前述の特定の問題が残っていることを意味します。サービスロケーターはより多くのコードを必要とし、他の開発者にそれが必要としないものを必要としていることを伝え、他の開発者がより悪いコードを書くことを奨励し、前進する柔軟性を少なくします。

簡単に言えばサービスロケータはアプリケーションの結合を増やすおよび他の開発者に高度に結合されたコードを書くように促す

1
Stephen