Vor kurzem 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:
API-Nutzungsproblem (was mir sehr gut geht)
Wenn eine Klasse einen Service Locator verwendet, sind ihre Abhängigkeiten sehr schwer zu erkennen, da die Klasse in den meisten Fällen nur einen PARAMETERLESS-Konstruktor aufweist .. Im Gegensatz zum ServiceLocator legt der DI-Ansatz Abhängigkeiten explizit über Konstruktorparameter frei, sodass Abhängigkeiten einfach sind gesehen in IntelliSense.
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 kennen, in der der Service Locator verwendet wird, und der Compiler wird Ihnen nicht helfen.
Aber warten Sie bitte eine Sekunde. Wenn Sie den DI-Ansatz verwenden, würden wir im Konstruktor eine Abhängigkeit mit einem anderen Parameter einführen (im Falle einer Konstruktorinjektion). Und das Problem wird immer noch da sein. Wenn wir vergessen, ServiceLocator einzurichten, vergessen wir möglicherweise, eine neue Zuordnung in unseren IoC-Container hinzuzufügen, und der DI-Ansatz hätte das gleiche Laufzeitproblem.
Der Autor erwähnte auch die Schwierigkeiten bei der Prüfung von Einheiten. Aber haben wir keine Probleme mit dem DI-Ansatz? Müssen wir nicht alle Tests aktualisieren, die diese Klasse instanziiert haben? Wir werden sie aktualisieren, um eine neue mutmaßliche Abhängigkeit zu bestehen, um unseren Test kompilierbar zu machen. Und ich sehe keine Vorteile von diesem Update und 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-Muster" hängt wirklich von den Umständen ab. Und ich würde definitiv nicht vorschlagen, es aus Ihrer Werkzeugliste zu streichen. Es kann sehr praktisch sein, wenn Sie sich mit altem Code beschäftigen. Wenn Sie das Glück haben, ganz am Anfang Ihres Projekts zu sein, ist der DI-Ansatz möglicherweise die bessere Wahl, da er einige Vorteile gegenüber dem Service Locator bietet.
Und hier sind die wichtigsten Unterschiede, die mich überzeugt haben, Service Locator nicht für meine neuen Projekte zu verwenden:
Für Details lesen Sie die ausgezeichneten Antworten, die unten angegeben sind.
Wenn Sie Muster als Anti-Muster definieren, nur weil es einige Situationen gibt, in denen es nicht passt, ist JA ein Anti-Muster. Aber mit dieser Begründung wären alle Muster auch Anti-Muster.
Stattdessen müssen wir prüfen, ob es gültige Verwendungen der Muster gibt, 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 mit dieser Klasse besteht darin, dass die Abhängigkeiten verborgen sind. Wenn Sie diese Klasse erstellen und verwenden:
var myType = new MyType();
myType.MyMethod();
Sie verstehen nicht, dass Abhängigkeiten bestehen, wenn sie mithilfe des Servicestandorts ausgeblendet werden. Wenn wir stattdessen 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 befriedigen.
In einer typischen Geschäftsanwendung sollten Sie die Verwendung des Service-Standorts aus genau diesem Grund vermeiden. Es sollte das Muster sein, das zu verwenden ist, wenn keine anderen Optionen vorhanden sind.
Nein.
Zum Beispiel würde das Umkehren von Kontrollcontainern ohne Service-Standort 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 die Steuerungen möglich? Richtig - Servicestandort.
Aber warte mal, wenn wir DI-Ansatz verwenden würden, würden wir ein .__ einführen. Abhängigkeit von einem anderen Parameter im Konstruktor (im Fall von Konstruktorinjektion). Und das Problem wird immer noch da sein.
Es gibt zwei weitere schwerwiegende Probleme:
Mit der Konstruktorinjektion über einen Container erhalten Sie das kostenlos.
Wenn wir dürfen. Vergessen Sie nicht, ServiceLocator einzurichten. Dann vergessen wir möglicherweise, ein neues .__ hinzuzufügen. Mapping in unserem IoC-Container und DI-Ansatz hätte das gleiche Laufzeitproblem.
Das ist richtig. Bei der Konstruktorinjektion müssen Sie jedoch nicht die gesamte Klasse scannen, um herauszufinden, welche Abhängigkeiten fehlen.
Und einige bessere Container validieren beim Start alle Abhängigkeiten (durch Scannen aller Konstruktoren). Bei diesen Containern erhalten Sie den Laufzeitfehler direkt und nicht zu einem späteren Zeitpunkt.
Der Autor erwähnte auch die Schwierigkeiten bei der Prüfung von Einheiten. Aber haben wir keine 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 arbeiten zu lassen? Das ist kein Spaß.
Ich möchte auch darauf hinweisen, dass, wenn Sie den alten Code umgestalten, dass das Service Locator-Muster nicht nur kein Anti-Muster ist, sondern auch eine praktische Notwendigkeit ist. Niemand wird jemals einen Zauberstab über Millionen von Codezeilen winken und plötzlich ist alles Code bereit für DI. Wenn Sie also anfangen möchten, DI in eine vorhandene Codebasis einzuführen, ändern Sie häufig Dinge, um 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 dieser Dienste zu erhalten, die HAVE zur Verwendung von DI konvertiert haben.
Beim Umgestalten großer Legacy-Anwendungen für die Verwendung von DI-Konzepten 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.
Aus Testsicht ist der Service Locator fehlerhaft. Siehe Misko Heverys Google Tech Talk. Nizza Erklärung mit Codebeispielen http://youtu.be/RlfLCWKxHJ0 ab Minute 8:45. Ich mochte seine Analogie: Wenn Sie $ 25 brauchen, fragen Sie direkt nach Geld, anstatt die Geldbörse zu geben, von der das Geld genommen wird. Er vergleicht auch Service Locator mit einem Heuhaufen, der über die Nadel verfügt, die Sie benötigen, und weiß, wie er abgerufen werden muss. Klassen, die Service Locator verwenden, sind aufgrund dessen schwer wiederzuverwenden.
Wartungsproblem (was mich verwirrt)
Es gibt zwei verschiedene Gründe, warum die Verwendung von Service Locator in dieser Hinsicht schlecht ist.
Einfach und einfach: Eine Klasse mit einem Service Locator ist schwieriger wiederzuverwenden als eine Klasse, die ihre Abhängigkeiten durch ihren Konstruktor akzeptiert.
Betrachten Sie den Fall, in dem Sie einen Dienst von
LibraryA
verwenden müssen, für den sich der Autor entschieden hat,ServiceLocatorA
und einen Dienst vonLibraryB
zu verwenden, dessen Autor sich fürServiceLocatorB
entschieden hat. Wir haben keine andere Wahl, als zwei verschiedene Service Locators in unserem Projekt einzusetzen. Wie viele Abhängigkeiten konfiguriert werden müssen, ist ein Ratespiel, wenn wir keine gute Dokumentation, keinen Quellcode oder keinen Autor für die Kurzwahl haben. Wenn diese Optionen fehlschlagen, müssen wir möglicherweise einen Dekompilierer nur verwenden, um die Abhängigkeiten herauszufinden. Möglicherweise müssen zwei völlig unterschiedliche Service Locator-APIs konfiguriert werden. Je nach Design ist es möglicherweise nicht möglich, den vorhandenen DI-Container einfach einzuwickeln. Es ist möglicherweise nicht möglich, eine Instanz einer Abhängigkeit zwischen den beiden Bibliotheken gemeinsam zu nutzen. Die Komplexität des Projekts könnte sich sogar noch verschärfen, 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 die gleichen zwei Dienste, die mit der Konstruktorinjektion gemacht wurden. Fügen Sie einen Verweis auf
LibraryA
hinzu. Fügen Sie einen Verweis aufLibraryB
hinzu. Geben Sie die Abhängigkeiten in Ihrer DI-Konfiguration an (indem Sie analysieren, was über Intellisense erforderlich ist). Erledigt.Mark Seemann verfügt über eine StackOverflow-Antwort, die diesen Vorteil anschaulich veranschaulicht . Dies gilt nicht nur für die Verwendung eines Service Locators aus einer anderen Bibliothek, sondern auch für die Verwendung von Fremdstandards in Services.
Mein Wissen ist nicht gut genug, um das beurteilen zu können, aber im Allgemeinen denke ich, wenn etwas in einer bestimmten Situation von Nutzen ist, bedeutet das nicht unbedingt, dass es kein Antimuster sein kann. Insbesondere wenn Sie sich mit Bibliotheken von Drittanbietern beschäftigen, haben Sie nicht die volle Kontrolle über alle Aspekte und verwenden möglicherweise nicht die beste Lösung.
Hier ist ein Absatz aus Adaptiver Code über C # :
"Leider ist der Service Locator manchmal ein unvermeidlicher Anti-Pattern. Bei einigen Anwendungstypen - insbesondere Windows Workflow Foundation - eignet sich die Infrastruktur nicht für Konstruktorinjektionen. In diesen Fällen besteht die einzige Alternative in der Verwendung eines Service Locator Es ist besser als gar keine Abhängigkeiten zu injizieren. Für alle meine Vitriol gegen das (Anti-) Muster ist es unendlich viel besser als das manuelle Erstellen von Abhängigkeiten. Schließlich ermöglicht es immer noch alle wichtigen Erweiterungspunkte, die von Schnittstellen bereitgestellt werden, und ähnliche Vorteile. "
- Hall, Gary McLean. Adaptiver Code über C #: Agile Codierung mit Entwurfsmustern und SOLID - Prinzipien (Developer Reference) (S. 309). Pearson Ausbildung.
Ja, Service Locator ist ein Anti-Pattern, das gegen die Kapselung und solid verstößt.
Der Autor begründet, dass "der Compiler Ihnen nicht helfen wird" - und das stimmt. Wenn Sie eine Klasse entwerfen, sollten Sie ihre Schnittstelle sorgfältig auswählen - neben anderen Zielen, um sie so unabhängig wie ... zu machen es macht Sinn.
Indem der Client den Verweis auf einen Dienst (auf eine Abhängigkeit) über eine explizite Schnittstelle akzeptiert, müssen Sie
Du hast 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 der Schnittstelle (Konstruktor) eingeführt wird - aber das hier ist hoffentlich genau die Abhängigkeit, die Sie brauchen und sichtbar und überprüfbar machen wollen.