Ist ServiceLocator ein Anti-Pattern?

137

Kürzlich habe ich Mark Seemanns Artikel über Service Locator Anti-Pattern gelesen .

Der Autor weist auf zwei Hauptgründe hin, warum ServiceLocator ein Anti-Pattern ist:

  1. API-Verwendungsproblem (mit dem ich vollkommen
    einverstanden bin) Wenn die Klasse einen Service Locator verwendet, ist es sehr schwierig, ihre Abhängigkeiten zu erkennen, da die Klasse in den meisten Fällen nur einen PARAMETERLESS-Konstruktor hat. Im Gegensatz zu ServiceLocator macht der DI-Ansatz Abhängigkeiten explizit über Konstruktorparameter verfügbar, sodass Abhängigkeiten in IntelliSense leicht erkennbar sind.

  2. Wartungsproblem (was mich
    verwirrt ) Betrachten Sie das folgende Beispiel

Wir haben eine Klasse 'MyType', die einen Service Locator-Ansatz verwendet:

public class MyType
{
    public void MyMethod()
    {
        var dep1 = Locator.Resolve<IDep1>();
        dep1.DoSomething();
    }
}

Jetzt möchten wir der Klasse 'MyType' eine weitere Abhängigkeit hinzufügen.

public class MyType
{
    public void MyMethod()
    {
        var dep1 = Locator.Resolve<IDep1>();
        dep1.DoSomething();

        // new dependency
        var dep2 = Locator.Resolve<IDep2>();
        dep2.DoSomething();
    }
}

Und hier beginnt mein Missverständnis. Der Autor sagt:

Es wird viel schwieriger zu sagen, ob Sie eine bahnbrechende Änderung einführen oder nicht. Sie müssen die gesamte Anwendung verstehen, in der der Service Locator verwendet wird, und der Compiler wird Ihnen nicht helfen.

Aber warten Sie eine Sekunde, wenn wir den DI-Ansatz verwenden, würden wir eine Abhängigkeit mit einem anderen Parameter im Konstruktor einführen (im Fall einer Konstruktorinjektion). Und das Problem wird immer noch da sein. Wenn wir möglicherweise vergessen, ServiceLocator einzurichten, vergessen wir möglicherweise, eine neue Zuordnung in unseren IoC-Container einzufügen, und der DI-Ansatz hätte das gleiche Laufzeitproblem.

Auch der Autor erwähnte über Unit-Test-Schwierigkeiten. Aber haben wir nicht Probleme mit dem DI-Ansatz? Müssen wir nicht alle Tests aktualisieren, die diese Klasse instanziiert haben? Wir werden sie aktualisieren, um eine neue verspottete Abhängigkeit zu übergeben, nur um unseren Test kompilierbar zu machen. Und ich sehe keine Vorteile aus diesem Update und dem Zeitaufwand.

Ich versuche nicht, den Service Locator-Ansatz zu verteidigen. Aber dieses Missverständnis lässt mich denken, dass ich etwas sehr Wichtiges verliere. Könnte jemand meine Zweifel zerstreuen?

UPDATE (ZUSAMMENFASSUNG):

Die Antwort auf meine Frage "Ist Service Locator ein Anti-Pattern" hängt wirklich von den Umständen ab. Und ich würde definitiv nicht vorschlagen, es von Ihrer Werkzeugliste zu streichen. Dies kann sehr praktisch sein, wenn Sie mit Legacy-Code arbeiten. Wenn Sie das Glück haben, ganz am Anfang Ihres Projekts zu stehen, ist der DI-Ansatz möglicherweise die bessere Wahl, da er einige Vorteile gegenüber Service Locator bietet.

Und hier sind die Hauptunterschiede, die mich überzeugt haben, Service Locator nicht für meine neuen Projekte zu verwenden:

  • Am offensichtlichsten und wichtigsten: Service Locator verbirgt Klassenabhängigkeiten
  • Wenn Sie einen IoC-Container verwenden, werden wahrscheinlich alle Konstruktoren beim Start gescannt, um alle Abhängigkeiten zu überprüfen und Ihnen sofort Feedback zu fehlenden Zuordnungen (oder falscher Konfiguration) zu geben. Dies ist nicht möglich, wenn Sie Ihren IoC-Container als Service Locator verwenden

Für Details lesen Sie ausgezeichnete Antworten, die unten gegeben werden.

Davidoff
quelle
"Müssen wir nicht alle Tests aktualisieren, die diese Klasse instanziiert haben?" Nicht unbedingt wahr, wenn Sie in Ihren Tests einen Builder verwenden. In diesem Fall müssten Sie nur den Builder aktualisieren.
Peter Karlsson
Du hast recht, es kommt darauf an. In großen Android-Apps beispielsweise zögerten die Benutzer bisher sehr, DI zu verwenden, da Leistungsprobleme bei mobilen Geräten mit niedrigen Spezifikationen auftreten. In solchen Fällen müssen Sie eine Alternative finden, um noch testbaren Code zu schreiben, und ich würde sagen, dass Service Locator in diesem Fall ein ausreichend guter Ersatz ist. (Hinweis: Dinge können sich für Android ändern, wenn das neue Dagger 2.0 DI-Framework ausgereift genug ist.)
G. Lombard
1
Beachten Sie, dass es seit der Veröffentlichung dieser Frage ein Update zu Mark Seemanns Service Locator gibt, einem Anti-Pattern-Beitrag, der beschreibt, wie der Service Locator gegen OOP verstößt, indem er die Kapselung unterbricht. Dies ist sein bisher bestes Argument (und der zugrunde liegende Grund für alle Symptome) dass er in allen vorherigen Argumenten verwendet). Update 26.10.2015: Das grundlegende Problem mit Service Locator besteht darin, dass die Kapselung verletzt wird .
NightOwl888

Antworten:

125

Wenn Sie Muster als Anti-Muster definieren, nur weil es Situationen gibt, in denen sie nicht passen, dann ist JA ein Anti-Muster. Aber mit dieser Überlegung wären alle Muster auch Anti-Muster.

Stattdessen müssen wir prüfen, ob die Muster gültig verwendet werden, und für Service Locator gibt es mehrere Anwendungsfälle. Aber schauen wir uns zunächst die Beispiele an, die Sie gegeben haben.

public class MyType
{
    public void MyMethod()
    {
        var dep1 = Locator.Resolve<IDep1>();
        dep1.DoSomething();

        // new dependency
        var dep2 = Locator.Resolve<IDep2>();
        dep2.DoSomething();
    }
}

Der Wartungs-Albtraum bei dieser Klasse ist, dass die Abhängigkeiten ausgeblendet sind. Wenn Sie diese Klasse erstellen und verwenden:

var myType = new MyType();
myType.MyMethod();

Sie verstehen nicht, dass es Abhängigkeiten gibt, wenn diese über den Dienstort ausgeblendet werden. Wenn wir stattdessen die Abhängigkeitsinjektion verwenden:

public class MyType
{
    public MyType(IDep1 dep1, IDep2 dep2)
    {
    }

    public void MyMethod()
    {
        dep1.DoSomething();

        // new dependency
        dep2.DoSomething();
    }
}

Sie können die Abhängigkeiten direkt erkennen und die Klassen nicht verwenden, bevor Sie sie erfüllt haben.

In einer typischen Geschäftsanwendung sollten Sie aus diesem Grund die Verwendung des Servicestandorts vermeiden. Es sollte das Muster sein, das verwendet werden soll, wenn keine anderen Optionen vorhanden sind.

Ist das Muster ein Anti-Muster?

Nein.

Zum Beispiel würde die Inversion von Steuercontainern ohne den Servicestandort nicht funktionieren. So lösen sie die Dienste intern auf.

Ein viel besseres Beispiel ist jedoch ASP.NET MVC und WebApi. Was macht Ihrer Meinung nach die Abhängigkeitsinjektion in den Controllern möglich? Das ist richtig - Servicestandort.

Deine Fragen

Aber warten Sie eine Sekunde, wenn wir den DI-Ansatz verwenden, würden wir eine Abhängigkeit mit einem anderen Parameter im Konstruktor einführen (im Fall einer Konstruktorinjektion). Und das Problem wird immer noch da sein.

Es gibt zwei weitere schwerwiegende Probleme:

  1. Mit dem Servicestandort fügen Sie außerdem eine weitere Abhängigkeit hinzu: den Service-Locator.
  2. Wie können Sie festlegen, welche Lebensdauer die Abhängigkeiten haben sollen und wie / wann sie bereinigt werden sollen?

Bei der Konstruktorinjektion mit einem Container erhalten Sie diese kostenlos.

Wenn wir möglicherweise vergessen, ServiceLocator einzurichten, vergessen wir möglicherweise, eine neue Zuordnung in unseren IoC-Container einzufügen, und der DI-Ansatz hätte das gleiche Laufzeitproblem.

Das ist richtig. Mit der Konstruktorinjektion müssen Sie jedoch nicht die gesamte Klasse scannen, um herauszufinden, welche Abhängigkeiten fehlen.

Einige bessere Container überprüfen auch alle Abhängigkeiten beim Start (indem sie alle Konstruktoren scannen). Mit diesen Containern erhalten Sie den Laufzeitfehler also direkt und nicht zu einem späteren Zeitpunkt.

Auch der Autor erwähnte über Unit-Test-Schwierigkeiten. Aber haben wir nicht Probleme mit dem DI-Ansatz?

Nein, da Sie nicht von einem statischen Service Locator abhängig sind. Haben Sie versucht, parallele Tests mit statischen Abhängigkeiten durchzuführen? Das ist kein Spaß.

jgauffin
quelle
2
Jgauffin, danke für deine Antwort. Sie haben beim Start auf eine wichtige Sache bei der automatischen Überprüfung hingewiesen. Ich habe nicht darüber nachgedacht und jetzt sehe ich einen weiteren Vorteil von DI. Sie haben auch ein Beispiel angegeben: "var myType = new MyType ()". Aber ich kann es nicht als gültig betrachten, da wir in einer echten App niemals Abhängigkeiten instanziieren (IoC Container erledigt dies die ganze Zeit für uns). Dh: In der MVC-App haben wir einen Controller, der von IMyService und MyServiceImpl von IMyRepository abhängt. Wir werden MyRepository und MyService niemals instanziieren. Wir erhalten Instanzen von Ctor-Parametern (wie von ServiceLocator) und verwenden sie. Nicht wir?
Davidoff
33
Ihr einziges Argument dafür, dass Service Locator kein Anti-Pattern ist, lautet: "Die Inversion von Steuercontainern würde ohne Service Location nicht funktionieren." Dieses Argument ist jedoch ungültig, da es bei Service Locator um Absichten und nicht um Mechanik geht, wie hier von Mark Seemann klar erläutert : "Ein DI-Container, der in einem Composition Root eingekapselt ist, ist kein Service Locator - es ist eine Infrastrukturkomponente."
Steven
4
Die @ jgauffin-Web-API verwendet den Dienstort für DI in Controllern nicht. DI macht das überhaupt nicht. Dies bedeutet Folgendes: Sie haben die Möglichkeit, einen eigenen ControllerActivator zu erstellen, der an die Konfigurationsdienste übergeben wird. Von dort aus können Sie eine Kompositionswurzel erstellen, egal ob es sich um Pure DI oder Container handelt. Außerdem mischen Sie die Verwendung von Service Location als Bestandteil Ihres Musters mit der Definition des "Service Locator Pattern". Mit dieser Definition könnte Composition Root DI als "Service Locator Pattern" betrachtet werden. Der springende Punkt dieser Definition ist also strittig.
Suamere
1
Ich möchte darauf hinweisen, dass dies eine allgemein gute Antwort ist. Ich habe nur einen irreführenden Punkt kommentiert, den Sie gemacht haben.
Suamere
2
@jgauffin DI und SL sind beide Versionswiedergaben von IoC. SL ist einfach der falsche Weg. Ein IoC - Container könnten SL sein, oder es könnte DI verwenden. Es hängt davon ab, wie es verdrahtet ist. Aber SL ist schlecht, schlecht schlecht. Es ist eine Möglichkeit, die Tatsache zu verbergen, dass Sie alles eng miteinander verbinden.
MirroredFate
37

Ich möchte auch darauf hinweisen, dass das Service Locator-Muster nicht nur kein Anti-Pattern ist, sondern auch eine praktische Notwendigkeit, wenn Sie Legacy-Code umgestalten. Niemand wird jemals einen Zauberstab über Millionen von Codezeilen schwingen und plötzlich wird der gesamte Code DI-fähig sein. Wenn Sie also mit der Einführung von DI in eine vorhandene Codebasis beginnen möchten, ändern Sie häufig Dinge, um langsam zu DI-Diensten zu werden, und der Code, der auf diese Dienste verweist, ist häufig KEIN DI-Dienst. Daher müssen DIESE Dienste den Service Locator verwenden, um Instanzen der Dienste abzurufen, die für die Verwendung von DI konvertiert wurden.

Wenn ich also große Legacy-Anwendungen umgestalte, um DI-Konzepte zu verwenden, würde ich sagen, dass Service Locator NICHT nur kein Anti-Pattern ist, sondern dass dies die einzige Möglichkeit ist, DI-Konzepte schrittweise auf die Codebasis anzuwenden.

jwells131313
quelle
12
Wenn Sie sich mit Legacy-Code beschäftigen, ist alles gerechtfertigt, um Sie aus diesem Chaos herauszuholen, auch wenn dies bedeutet, dass Sie Zwischenschritte (und unvollständige Schritte) unternehmen. Der Service Locator ist ein solcher Zwischenschritt. So können Sie Schritt für Schritt aus dieser Hölle herauskommen, solange Sie sich daran erinnern, dass es eine gute alternative Lösung gibt, die dokumentiert, wiederholbar und als wirksam erwiesen ist . Diese alternative Lösung ist Dependency Injection, und genau aus diesem Grund ist der Service Locator immer noch ein Anti-Pattern. richtig gestaltete Software verwendet dies nicht.
Steven
RE: "Wenn Sie sich mit Legacy-Code beschäftigen, ist alles gerechtfertigt, um Sie aus diesem Chaos herauszuholen." Manchmal frage ich mich, ob es nur ein bisschen Legacy-Code gibt, der jemals existiert hat, aber weil wir alles rechtfertigen können, um ihn zu beheben, tun wir es irgendwie hat es das nie geschafft.
Drew Delano
8

Aus Testsicht ist Service Locator schlecht. Siehe Misko Heverys Google Tech Talk, eine nette Erklärung mit Codebeispielen http://youtu.be/RlfLCWKxHJ0 ab Minute 8:45. Ich mochte seine Analogie: Wenn Sie 25 Dollar brauchen, fragen Sie direkt nach Geld, anstatt Ihre Brieftasche zu geben, von wo Geld genommen wird. Er vergleicht Service Locator auch mit einem Heuhaufen, der die Nadel hat, die Sie benötigen, und weiß, wie man sie abruft. Klassen, die Service Locator verwenden, sind daher schwer wiederzuverwenden.

user3511397
quelle
10
Dies ist eine recycelte Meinung und wäre als Kommentar besser gewesen. Ich denke auch, dass Ihre (seine?) Analogie dazu dient, zu beweisen, dass einige Muster für einige Probleme besser geeignet sind als für andere.
8bitjunkie
6

Wartungsproblem (was mich verwirrt)

Es gibt zwei verschiedene Gründe, warum die Verwendung von Service Locator in dieser Hinsicht schlecht ist.

  1. In Ihrem Beispiel codieren Sie einen statischen Verweis auf den Service Locator fest in Ihre Klasse. Dadurch wird Ihre Klasse eng mit dem Service Locator verbunden, was wiederum bedeutet, dass sie ohne den Service Locator nicht funktioniert . Darüber hinaus sind Ihre Komponententests (und alle anderen Benutzer der Klasse) implizit vom Service Locator abhängig. Eine Sache, die hier unbemerkt zu bleiben schien, ist, dass Sie bei der Verwendung der Konstruktorinjektion beim Komponententest keinen DI-Container benötigen , was Ihre Komponententests (und die Fähigkeit der Entwickler, sie zu verstehen) erheblich vereinfacht. Dies ist der realisierte Vorteil von Unit-Tests, den Sie durch die Verwendung der Konstruktorinjektion erhalten.
  2. Was den Grund betrifft, warum der Konstruktor Intellisense wichtig ist, scheinen die Leute hier den Punkt völlig verfehlt zu haben. Eine Klasse wird einmal geschrieben, kann jedoch in mehreren Anwendungen (dh mehreren DI-Konfigurationen) verwendet werden . Im Laufe der Zeit zahlt es sich aus, wenn Sie sich die Konstruktordefinition ansehen können, um die Abhängigkeiten einer Klasse zu verstehen, anstatt sich die (hoffentlich aktuelle) Dokumentation anzusehen oder, falls dies nicht der Fall ist, zum ursprünglichen Quellcode zurückzukehren (was möglicherweise nicht der Fall ist) praktisch sein), um die Abhängigkeiten einer Klasse zu bestimmen. Die Klasse mit dem Service Locator ist im Allgemeinen einfacher zu schreiben , aber Sie zahlen mehr als die Kosten für diese Bequemlichkeit bei der laufenden Wartung des Projekts.

Schlicht und einfach: Eine Klasse mit einem Service Locator ist schwieriger wiederzuverwenden als eine Klasse , die ihre Abhängigkeiten über ihren Konstruktor akzeptiert.

Stellen Sie sich den Fall vor, in dem Sie einen Dienst verwenden müssen, von LibraryAdem der Autor entschieden hat, dass er verwendet wird, ServiceLocatorAund einen Dienst, von LibraryBdessen Autor entschieden wird, dass er verwendet wird ServiceLocatorB. Wir haben keine andere Wahl, als 2 verschiedene Service Locators in unserem Projekt zu verwenden. Wie viele Abhängigkeiten konfiguriert werden müssen, ist ein Ratespiel, wenn wir keine gute Dokumentation, keinen guten Quellcode oder keinen Autor für die Kurzwahl haben. Wenn diese Optionen nicht verfügbar sind, müssen wir möglicherweise nur einen Dekompiler verwendenum herauszufinden, was die Abhängigkeiten sind. Möglicherweise müssen wir zwei völlig unterschiedliche Service Locator-APIs konfigurieren. Je nach Design ist es möglicherweise nicht möglich, Ihren vorhandenen DI-Container einfach zu verpacken. Es ist möglicherweise überhaupt nicht möglich, eine Instanz einer Abhängigkeit zwischen den beiden Bibliotheken zu teilen. Die Komplexität des Projekts könnte noch weiter verschärft werden, wenn sich die Service Locators nicht tatsächlich in denselben Bibliotheken befinden wie die von uns benötigten Services. Wir ziehen implizit zusätzliche Bibliotheksreferenzen in unser Projekt.

Betrachten Sie nun dieselben zwei Dienste, die mit der Konstruktorinjektion ausgeführt wurden. Fügen Sie einen Verweis auf hinzu LibraryA. Fügen Sie einen Verweis auf hinzu LibraryB. Geben Sie die Abhängigkeiten in Ihrer DI-Konfiguration an (indem Sie analysieren, was über Intellisense benötigt wird). Getan.

Mark Seemann hat eine StackOverflow-Antwort, die diesen Vorteil in grafischer Form klar veranschaulicht. Dies gilt nicht nur für die Verwendung eines Service Locator aus einer anderen Bibliothek, sondern auch für die Verwendung fremder Standardeinstellungen in Services.

NightOwl888
quelle
1

Mein Wissen ist nicht gut genug, um dies zu beurteilen, aber im Allgemeinen denke ich, wenn etwas in einer bestimmten Situation einen Nutzen hat, bedeutet dies nicht unbedingt, dass es kein Anti-Muster sein kann. Insbesondere wenn Sie mit Bibliotheken von Drittanbietern arbeiten, haben Sie nicht die volle Kontrolle über alle Aspekte und verwenden möglicherweise die nicht sehr beste Lösung.

Hier ist ein Absatz aus Adaptive Code Via C # :

"Leider ist der Service Locator manchmal ein unvermeidbares Anti-Pattern. In einigen Anwendungstypen - insbesondere Windows Workflow Foundation - eignet sich die Infrastruktur nicht für die Konstruktorinjektion. In diesen Fällen besteht die einzige Alternative darin, einen Service Locator zu verwenden Besser als überhaupt keine Abhängigkeiten zu injizieren. Bei all meinem Vitriol gegen das (Anti-) Muster ist es unendlich besser als das manuelle Erstellen von Abhängigkeiten. Schließlich werden immer noch die wichtigen Erweiterungspunkte aktiviert, die von Schnittstellen bereitgestellt werden, die Dekoratoren, Adapter, und ähnliche Vorteile. "

- Hall, Gary McLean. Adaptiver Code über C #: Agile Codierung mit Entwurfsmustern und SOLID-Prinzipien (Entwicklerreferenz) (S. 309). Pearson Ausbildung.

Siavash Mortazavi
quelle
0

Der Autor begründet, dass "der Compiler Ihnen nicht hilft" - und das ist wahr. Wenn Sie eine Klasse entwerfen, sollten Sie ihre Benutzeroberfläche sorgfältig auswählen - unter anderem, um sie so unabhängig wie möglich zu machen.

Indem der Client den Verweis auf einen Dienst (auf eine Abhängigkeit) über eine explizite Schnittstelle akzeptiert, können Sie

  • implizit überprüfen lassen, so dass der Compiler "hilft".
  • Sie müssen den Client auch nicht mehr über den "Locator" oder ähnliche Mechanismen informieren, damit der Client tatsächlich unabhängiger ist.

Sie haben Recht, dass DI seine Probleme / Nachteile hat, aber die genannten Vorteile überwiegen bei weitem ... IMO. Sie haben Recht, dass mit DI eine Abhängigkeit in die Schnittstelle (Konstruktor) eingeführt wurde - aber dies ist hoffentlich genau die Abhängigkeit, die Sie benötigen und die Sie sichtbar und überprüfbar machen möchten.

Zrin
quelle
Zrin, danke für deine Gedanken. Soweit ich weiß, sollte ich mit einem "richtigen" DI-Ansatz meine Abhängigkeiten nur bei Komponententests instanziieren. Der Compiler hilft mir also nur bei meinen Tests. Aber wie ich in meiner ursprünglichen Frage beschrieben habe, gibt mir diese "Hilfe" bei fehlerhaften Tests nichts. Macht es?
Davidoff
Das Argument 'statische Information' / 'Überprüfung der Kompilierungszeit' ist ein Strohmann. Wie @davidoff hervorgehoben hat, ist DI gleichermaßen anfällig für Laufzeitfehler. Ich möchte auch hinzufügen, dass moderne IDEs Tooltip-Ansichten von Kommentaren / Zusammenfassungsinformationen für Mitglieder bereitstellen, und selbst in solchen, die dies nicht tun, wird sich jemand noch mit Dokumentation befassen, bis er die API nur "kennt". Dokumentation ist Dokumentation, unabhängig davon, ob es sich um einen erforderlichen Konstruktorparameter oder um eine Anmerkung zum Konfigurieren einer Abhängigkeit handelt.
Dienstag,
Denken Sie an Implementierung / Codierung und Qualitätssicherung - die Lesbarkeit von Code ist entscheidend - insbesondere für die Definition der Schnittstelle. Wenn Sie auf automatische Überprüfungen der Kompilierungszeit verzichten können und Ihre Benutzeroberfläche angemessen kommentieren / dokumentieren, können Sie die Nachteile der versteckten Abhängigkeit von einer Art globaler Variable mit nicht leicht sichtbaren / vorhersehbaren Inhalten zumindest teilweise beheben. Ich würde sagen, Sie brauchen gute Gründe, um dieses Muster zu verwenden, das diese Nachteile überwiegt.
Zrin
0

Ja, Service Locator ist ein Anti-Pattern, das die Kapselung verletzt und fest ist .

Alireza Rahmani Khalili
quelle
1
Ich verstehe, was Sie meinen, aber es wäre hilfreich, weitere Details hinzuzufügen, z. B. warum es gegen SOLID verstößt
Reggaeguitar