it-swarm.com.de

Was ist der Unterschied zwischen der Verwendung der Abhängigkeitsinjektion mit einem Container und der Verwendung eines Service Locator?

Ich verstehe, dass das direkte Instanziieren von Abhängigkeiten innerhalb einer Klasse als schlechte Praxis angesehen wird. Dies ist sinnvoll, da dies alles eng miteinander verbindet, was wiederum das Testen sehr schwierig macht.

Fast alle Frameworks, auf die ich gestoßen bin, scheinen die Abhängigkeitsinjektion mit einem Container gegenüber der Verwendung von Service Locators zu bevorzugen. Beide scheinen dasselbe zu erreichen, indem sie dem Programmierer erlauben, anzugeben, welches Objekt zurückgegeben werden soll, wenn eine Klasse eine Abhängigkeit erfordert.

Was ist der Unterschied zwischen den beiden? Warum sollte ich einen über den anderen wählen?

112
tom6025222

Wenn das Objekt selbst dafür verantwortlich ist, seine Abhängigkeiten anzufordern, anstatt sie über einen Konstruktor zu akzeptieren, werden einige wichtige Informationen ausgeblendet. Es ist nur geringfügig besser als der sehr eng gekoppelte Fall, new zu verwenden, um seine Abhängigkeiten zu instanziieren. Es reduziert die Kopplung, da Sie tatsächlich die Abhängigkeiten ändern können, die es erhält, aber es hat immer noch eine Abhängigkeit, die es nicht erschüttern kann: den Service Locator. Das ist die Sache, von der alles abhängt.

Ein Container, der Abhängigkeiten über Konstruktorargumente bereitstellt, bietet die größte Klarheit. Wir sehen ganz vorne, dass ein Objekt sowohl ein AccountRepository als auch ein PasswordStrengthEvaluator benötigt. Bei Verwendung eines Service Locator sind diese Informationen weniger sofort ersichtlich. Sie würden sofort einen Fall sehen, in dem ein Objekt 17 Abhängigkeiten aufweist, und sich selbst sagen: "Hmm, das scheint viel zu sein. Was ist dort los?" Anrufe an einen Service Locator können auf die verschiedenen Methoden verteilt werden und sich hinter der bedingten Logik verstecken. Möglicherweise stellen Sie nicht fest, dass Sie eine "Gottklasse" erstellt haben - eine, die alles tut. Vielleicht könnte diese Klasse in drei kleinere Klassen umgestaltet werden, die fokussierter und daher überprüfbarer sind.

Betrachten Sie nun das Testen. Wenn ein Objekt einen Service Locator verwendet, um seine Abhängigkeiten abzurufen, benötigt Ihr Testframework auch einen Service Locator. In einem Test konfigurieren Sie den Service Locator so, dass die Abhängigkeiten für das zu testende Objekt bereitgestellt werden - möglicherweise ein FakeAccountRepository und ein VeryForgivingPasswordStrengthEvaluator - und führen dann den Test aus. Dies ist jedoch mehr Arbeit als das Festlegen von Abhängigkeiten im Konstruktor des Objekts. Und Ihr Testframework wird auch vom Service Locator abhängig. Es ist eine andere Sache, die Sie in jedem Test konfigurieren müssen, was das Schreiben von Tests weniger attraktiv macht.

Suchen Sie nach "Serivce Locator ist ein Anti-Pattern" für Mark Seemans Artikel darüber. Wenn Sie in der .Net-Welt sind, holen Sie sich sein Buch. Es ist sehr gut.

126
Carl Raymond

Stellen Sie sich vor, Sie arbeiten in einer Fabrik, in der Schuhe hergestellt werden .

Sie sind für die Montage der Schuhe verantwortlich und benötigen daher viele Dinge, um dies zu tun.

  • Leder
  • Maßband
  • Kleben
  • Nägel
  • Hammer
  • Schere
  • Schnürsenkel

Und so weiter.

Sie arbeiten in der Fabrik und können loslegen. Sie haben eine Liste mit Anweisungen zum weiteren Vorgehen, aber noch keine Materialien oder Werkzeuge.

Ein Service Locator ist wie ein Vorarbeiter, der Ihnen helfen kann, das zu bekommen, was Sie brauchen.

Sie fragen den Service Locator jedes Mal, wenn Sie etwas benötigen, und er geht los, um es für Sie zu finden. Dem Service Locator wurde im Voraus mitgeteilt, wonach Sie wahrscheinlich fragen und wie Sie ihn finden.

Du solltest besser hoffen, dass du nicht nach etwas Unerwartetem fragst. Wenn der Locator nicht im Voraus über ein bestimmtes Werkzeug oder Material informiert wurde, kann er es nicht für Sie besorgen und zuckt nur mit den Schultern.

(service locator

Ein Dependency Injection (DI) Container ist wie eine große Kiste, die zu Beginn des Tages mit allem gefüllt wird, was jeder braucht.

Beim Start der Fabrik greift der als Kompositionswurzel bekannte Big Boss nach dem Container und verteilt alles an den Vorgesetzte .

Die Vorgesetzten haben jetzt das, was sie brauchen, um ihre Aufgaben für den Tag zu erledigen. Sie nehmen, was sie haben und geben das Notwendige an ihre Untergebenen weiter.

Dieser Prozess wird fortgesetzt, wobei Abhängigkeiten die Produktionslinie hinunterlaufen. Schließlich erscheint ein Behälter mit Materialien und Werkzeugen für Ihren Vorarbeiter.

Ihr Vorarbeiter verteilt jetzt genau das, was Sie benötigen, an Sie und andere Mitarbeiter, ohne dass Sie danach fragen.

Grundsätzlich ist alles, was Sie brauchen, in einer Kiste, die auf Sie wartet, sobald Sie zur Arbeit erscheinen. Sie mussten nichts darüber wissen, wie man sie bekommt.

(dependency injection container

79
Rowan Freeman

Ein paar zusätzliche Punkte, die ich beim Durchsuchen des Webs gefunden habe:

  • Das Einfügen von Abhängigkeiten in den Konstruktor erleichtert das Verständnis der Anforderungen einer Klasse. Moderne IDEs geben an, welche Argumente der Konstruktor akzeptiert und welche Typen sie haben. Wenn Sie einen Service Locator verwenden, müssen Sie die Klasse durchlesen, bevor Sie wissen, welche Abhängigkeiten erforderlich sind.
  • Die Abhängigkeitsinjektion scheint mehr dem Prinzip "Tell Don't Ask" zu entsprechen als Service Locators. Indem Sie festlegen, dass eine Abhängigkeit von einem bestimmten Typ sein soll, "sagen" Sie, welche Abhängigkeiten erforderlich sind. Es ist unmöglich, die Klasse zu instanziieren, ohne die erforderlichen Abhängigkeiten zu übergeben. Mit einem Service Locator "fragen" Sie nach einem Service und wenn der Service Locator nicht richtig konfiguriert ist, erhalten Sie möglicherweise nicht das, was erforderlich ist.
10
tom6025222

Ich komme zu spät zu dieser Party, aber ich kann nicht widerstehen.

Was ist der Unterschied zwischen der Verwendung der Abhängigkeitsinjektion mit einem Container und der Verwendung eines Service Locator?

Manchmal überhaupt keine. Was den Unterschied ausmacht, ist, was über was weiß.

Sie wissen, dass Sie einen Service Locator verwenden, wenn der Client, der nach der Abhängigkeit sucht, über den Container Bescheid weiß. Ein Client, der weiß, wie er seine Abhängigkeiten findet, selbst wenn er einen Container durchläuft, um sie abzurufen, ist das Service-Locator-Muster.

Bedeutet dies, dass Sie keinen Container verwenden können, wenn Sie Service Locator vermeiden möchten? Nein. Sie müssen nur verhindern, dass Kunden etwas über den Container erfahren. Der Hauptunterschied besteht darin, wo Sie den Container verwenden.

Nehmen wir an, Client braucht Dependency. Der Container hat ein Dependency.

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

Wir haben gerade das Service Locator-Muster befolgt, weil Client weiß, wie man Dependency findet. Sicher, es wird ein fest codiertes ClassPathXmlApplicationContext verwendet, aber selbst wenn Sie einfügen, haben Sie immer noch einen Service Locator, weil Clientbeanfactory.getBean() aufruft.

Um Service Locator zu vermeiden, müssen Sie diesen Container nicht verlassen. Sie müssen es nur aus Client heraus verschieben, damit Client nichts davon weiß.

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>

Beachten Sie, dass Client jetzt keine Ahnung hat, dass der Container existiert:

class Client { 
    Client(Dependency dependency) { 

        this.dependency = dependency;        
    }
    Dependency dependency;
}

Bewegen Sie den Container aus allen Clients heraus und kleben Sie ihn in main, wo ein Objektdiagramm aller Ihrer langlebigen Objekte erstellt werden kann. Wählen Sie eines dieser Objekte aus, um es zu extrahieren, und rufen Sie eine Methode auf, und Sie beginnen mit dem Ticken des gesamten Diagramms.

Dadurch wird die gesamte statische Konstruktion in das XML-Container verschoben, und alle Ihre Kunden wissen nicht, wie sie ihre Abhängigkeiten finden sollen.

Aber main weiß immer noch, wie man Abhängigkeiten findet! Ja tut es. Indem Sie dieses Wissen nicht verbreiten, haben Sie das Kernproblem des Service Locator vermieden. Die Entscheidung, einen Container zu verwenden, wird jetzt an einem Ort getroffen und kann geändert werden, ohne dass Hunderte von Clients neu geschrieben werden müssen.

5
candied_orange

Ich denke, dass der einfachste Weg, den Unterschied zwischen den beiden zu verstehen und warum ein DI-Container so viel besser ist als ein Service Locator, darin besteht, darüber nachzudenken, warum wir überhaupt eine Abhängigkeitsinversion durchführen.

Wir machen eine Abhängigkeitsinversion, so dass jede Klasse gibt explizit genau an, wovon sie abhängig ist für die Operation. Wir tun dies, weil dies die lockerste Kopplung schafft, die wir erreichen können. Je lockerer die Kopplung ist, desto einfacher ist es, etwas zu testen und umzugestalten (und erfordert im Allgemeinen das geringste Umgestalten in der Zukunft, da der Code sauberer ist).

Schauen wir uns die folgende Klasse an:

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);
  }
}

In dieser Klasse geben wir ausdrücklich an, dass wir einen IOutputProvider und nichts anderes benötigen, damit diese Klasse funktioniert. Dies ist vollständig testbar und hängt von einer einzelnen Schnittstelle ab. Ich kann diese Klasse an eine beliebige Stelle in meiner Anwendung verschieben, einschließlich eines anderen Projekts. Sie benötigt lediglich Zugriff auf die IOutputProvider-Oberfläche. Wenn andere Entwickler dieser Klasse etwas Neues hinzufügen möchten, was eine zweite Abhängigkeit erfordert, sie müssen explizit sein darüber, was sie im Konstruktor benötigen.

Schauen Sie sich dieselbe Klasse mit einem Service Locator an:

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);
  }
}

Jetzt habe ich den Service Locator als Abhängigkeit hinzugefügt. Hier sind die Probleme, die sofort offensichtlich sind:

  • Das allererste Problem dabei ist, dass es wird mehr Code benötigt, um das gleiche Ergebnis zu erzielen. Mehr Code ist schlecht. Es ist nicht viel mehr Code, aber es ist immer noch mehr.
  • Das zweite Problem ist, dass meine Abhängigkeit ist nicht mehr explizit. Ich muss noch etwas in die Klasse spritzen. Außer jetzt ist das, was ich will, nicht explizit. Es ist in einer Eigenschaft der Sache versteckt, die ich angefordert habe. Jetzt benötige ich Zugriff auf den ServiceLocator und den IOutputProvider, wenn ich die Klasse in eine andere Assembly verschieben möchte.
  • Das dritte Problem ist, dass ein zusätzliche Abhängigkeit von einem anderen Entwickler übernommen werden kann, der nicht einmal merkt es nimmt, wenn er der Klasse Code hinzufügt.
  • Schließlich ist dieser Code schwieriger zu testen (auch wenn ServiceLocator eine Schnittstelle ist), da wir ServiceLocator und IOutputProvider verspotten müssen, anstatt nur IOutputProvider

Warum machen wir den Service Locator nicht zu einer statischen Klasse? Lass uns mal sehen:

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

Das ist viel einfacher, oder?

Falsch.

Angenommen, IOutputProvider wird von einem sehr lang laufenden Webdienst implementiert, der die Zeichenfolge in fünfzehn verschiedene Datenbanken auf der ganzen Welt schreibt und deren Fertigstellung sehr lange dauert.

Versuchen wir, diese Klasse zu testen. Für den Test benötigen wir eine andere Implementierung von IOutputProvider. Wie schreiben wir den Test?

Um dies zu erreichen, müssen wir eine ausgefallene Konfiguration in der statischen ServiceLocator-Klasse vornehmen, um eine andere Implementierung von IOutputProvider zu verwenden, wenn es vom Test aufgerufen wird. Sogar das Schreiben dieses Satzes war schmerzhaft. Die Implementierung wäre qualvoll und ein Wartungsalptraum. Wir sollten niemals eine Klasse speziell zum Testen ändern müssen, insbesondere wenn diese Klasse nicht die Klasse ist, die wir tatsächlich testen möchten.

Jetzt haben Sie entweder a) einen Test, der aufdringliche Codeänderungen in der nicht verwandten ServiceLocator-Klasse verursacht; oder b) überhaupt kein Test. Und Sie haben auch eine weniger flexible Lösung.

Daher muss die Service Locator-Klasse in den Konstruktor eingefügt werden. Was bedeutet, dass wir mit den oben erwähnten spezifischen Problemen zurückbleiben. Der Service Locator benötigt mehr Code, teilt anderen Entwicklern mit, dass er Dinge benötigt, die er nicht benötigt, ermutigt andere Entwickler, schlechteren Code zu schreiben, und gibt uns weniger Flexibilität, um vorwärts zu kommen.

Einfach ausgedrückt Service Locators erhöhen die Kopplung in einer Anwendung und ermutigen andere Entwickler, stark gekoppelten Code zu schreiben.

1
Stephen