Ha, was für eine anmutige Verkleidung des Service Locator unter DI. Es könnte sogar wie ein DI erscheinen! :-)
Dies ist der erste Kommentar zu meiner vorherigen Veröffentlichung, Dependency Injection, JavaScript und ES6 Modules . Vielen Dank an Kollegen Symbix 'u für diesen Kommentar, tk. er war es, der die Ursache für das Eintauchen in die Feinheiten des Unterschieds zwischen dem einen und dem anderen wurde. Unter dem Schnitt meine Antwort auf die Frage im Titel.

(KDPV macht wenig Sinn und ist unter anderem in erster Linie zur visuellen Identifizierung dieser Publikation gedacht)
Zunächst werden wir uns mit den Definitionen befassen ( einige der Codebeispiele, die ich in PHP bereitstellen werde, einige in JS, da diese beiden Sprachen derzeit in meinem aktiven Gepäck sind, und einige in anderen, weil ich diese Beispiele aus dem Internet gestohlen habe ).
Abhängigkeitsinjektion
DI auf den Punkt gebracht - Anstelle des Require / Import-Moduls fügen Sie die Abhängigkeit über den Konstruktorparameter (oder den Eigenschaftssetzer) ein. Das heißt, hinter diesem großen Wort stehen die einfachen „Klassenabhängigkeiten durch Konstruktorparameter übergeben“.
Dieser Kommentar des Kollegen von risedphantom vermittelt ziemlich genau die Essenz des Phänomens. Um dem Entwickler das Verständnis des Codes zu erleichtern, werden alle Abhängigkeiten explizit beschrieben - in Form von Konstruktorparametern (normalerweise, aber nicht immer):
class MainClass { public function __construct($dep1, $dep2, $dep3) {} }
Das ist alles. DI - er ist darüber. Die Abhängigkeiten, die wir brauchen, werden von jemandem dort bereitgestellt. Und wohin bringt sie dieser „Jemand da draußen“ - DI ist das egal.
Bei der Analyse des Codes ist es wichtig zu verstehen, was genau „intern“ ist (und was wir sicher ändern können) und was über die Verantwortung dieses Codes hinausgeht / geht. Das ist es, was DI begeistert. Warum werden Abhängigkeiten meistens durch den Konstruktor und nicht in Setzern übergeben? Weil es für den Entwickler bequemer ist, sieht er sofort alle Abhängigkeiten des analysierten Codes an einem Ort. Wir sind es gewohnt zu denken, dass DI etwas auf Klassenebene ist, aber Funktions- / Methodenparameter sind auch DI:
function f(dep1, dep2, dep3) {}
Ein Konstruktor ist unter allen anderen eine so spezielle Methode.
Service Locator
Service Locator ist ein bekanntes Anti-Pattern. Was macht er Bietet Zugriff auf ein Objekt für andere Objekte. Hier ist eine typische Schnittstelle für einen solchen Locator:
interface ServiceLocator { public function addInstance(string $class, Service $service); public function addClass(string $class, array $params); public function has(string $interface): bool; public function get(string $class): Service; }
Ein Locator ist ein Container, in den Sie vorgefertigte Objekte einfügen (oder die Regeln für deren Erstellung festlegen) und dann auf diese Objekte zugreifen können. Für einen Locator spielt es im Großen und Ganzen keine Rolle, wie die Objekte darin angezeigt werden - sie werden explizit von außen erstellt und in den Container gestellt oder vom Container selbst gemäß den angegebenen Regeln erstellt.
Der Service Locator ist dafür verantwortlich, Objekte zu speichern und Zugriff darauf zu gewähren. Das ist alles.
Abhängigkeitsinjektionscontainer
Was ist ein DI-Container? Laut einer Freeware-Nacherzählung des Inhalts von " Prinzipien, Praktiken und Mustern der Abhängigkeitsinjektion ": "Eine Softwarebibliothek, die DI-Funktionalität bietet und die Automatisierung vieler Aufgaben im Zusammenhang mit Objektzusammensetzung, Abfangen und Lebensdauerverwaltung ermöglicht. DI-Container werden auch als Inversion von bezeichnet Kontrollcontainer (IoC). (§3.2.2) "
Das heißt, der DI-Container ist in erster Linie für die Erstellung von Objekten verantwortlich, aber nur im zweiten - für deren Speicherung. In einem DI-Container werden möglicherweise überhaupt keine erstellten Objekte gespeichert, wenn die Anwendung selbst keine Objekte benötigt, die der gesamten Anwendung gemeinsam sind (z. B. eine Konfiguration oder ein Logger).
Hier ist zum Beispiel die Symfony DI-Containerschnittstelle:
interface ContainerInterface extends PsrContainerInterface { public function set(string $id, ?object $service); public function get($id, int $invalidBehavior = self::EXCEPTION_ON_INVALID_REFERENCE); public function has($id); public function initialized(string $id); public function getParameter(string $name); public function hasParameter(string $name); public function setParameter(string $name, $value); }
Bei Bedarf können Sie sich sehr leicht davon überzeugen, dass die Schnittstelle des DI-Containers der Schnittstelle der Locator-Dienste sehr ähnlich ist - das gleiche get
, has
und set
/ add
.
Warum ist der Service Locator schlecht?
Aber nichts. Nicht die Vorlage selbst ist schlecht, aber die Art und Weise, wie sie manchmal verwendet wird. Insbesondere gibt es die Meinung, dass " Service Locator die Kapselung in statisch typisierten Sprachen verletzt, weil dieses Muster die Voraussetzungen nicht klar ausdrückt ." Und ein Beispiel Verstoß:
public class OrderProcessor : IOrderProcessor { public void Process(Order order) { var validator = Locator.Resolve<IOrderValidator>(); if (validator.Validate(order)) { var shipper = Locator.Resolve<IOrderShipper>(); shipper.Ship(order); } } }
Das ist so schlecht, aber gut - so:
public class OrderProcessor : IOrderProcessor { public OrderProcessor(IOrderValidator validator, IOrderShipper shipper) public void Process(Order order) }
Hey, wir haben gerade die Klammern des Augenblicks des Erstellens der Objekte mit den IOrderShipper
IOrderValidator
und IOrderShipper
! Es ist möglich, dass es in dieser Anwendung irgendwo anders so etwas wie diesen Code gibt:
var validator = Locator.Resolve<IOrderValidator>(); var shipper = Locator.Resolve<IOrderShipper>(); var processor = new OrderProcessor(validator, shipper); processor.Process(order);
Kompositionswurzel
Apropos Abhängigkeitsinjektion: Wir können nicht anders, als zu einem Konzept wie Composition Root zu gelangen (im Folgenden werde ich es den "Sammelpunkt" nennen). Dies ist derselbe „ Jemand “, an den die Verantwortlichkeiten für das Erstellen von Abhängigkeiten und deren Implementierung delegiert sind.
Am Sammelpunkt werden alle Abhängigkeiten explizit definiert, die entsprechenden Objekte werden dort erstellt und implementiert, wo sie warten. Hier sind die Unterschiede zwischen dem Services Locator und dem DI-Container minimal. Sowohl das als auch ein anderes ermöglichen das Erstellen neuer Objekte, das Speichern der erstellten Objekte und das Extrahieren gespeicherter Objekte. Ich würde mich sogar verpflichten zu behaupten, dass es an dieser Stelle keine grundlegenden Unterschiede zwischen ihnen gibt.
Der Hauptunterschied
Und wo sind die Unterschiede zwischen dem DI-Container und dem Container des Services Locator am offensichtlichsten? An jedem anderen Ort und insbesondere bei statischem Zugang zum Container:
$obj = Container::getInstance()->get($objId);
Hier ist es. In dieser Form wird jeder Container überall (es sei denn, es handelt sich um einen Sammelpunkt, aber die Verwendung eines statischen Zugriffs ist dort sehr zweifelhaft) zu einem Anti-Pattern namens Service Locator.
Ein VolCh- Kollege beantwortete meine Frage kurz und prägnant:
Und wie unterscheidet sich True DI Ihrer Meinung nach von einem als DI getarnten Service Locator?
so:
Containerzugang
Tatsächlich ist diese gesamte Veröffentlichung nur eine detailliertere Darstellung seiner Antwort hier.
Legale Verwendung des Containers
Ob ein Container ein DI-Container oder ein Container für den Services Locator ist, hängt daher stark davon ab, wo und wie wir ihn verwenden. Wie ich oben sagte, verschwindet am Sammelpunkt der Unterschied zwischen den Containertypen. Aber was übergeben wir, wenn der Container selbst eine Abhängigkeit für eine Klasse ist?
class Foo { public function __construct(Container $container){} }
Auch hier hängt alles davon ab, wie wir den Container verwenden. Hier sind zwei Beispiele, von denen eines genau zum Container des Services Locator passt und ein Anti-Pattern ist, während das andere unter bestimmten Bedingungen das Recht auf Leben hat.
class Foo { private $dep; public function __construct(Container $container) { $this->dep = $container->get('depId'); } }
class Foo { private $container; public function __construct(Container $container) { $this->container = $container; } public function initModules($modules) { foreach ($modules as $initClassId) { $init= $this->container->get($initClassId); $init->exec(); } } }
Zusammenfassung
Tatsächlich ist alles etwas komplizierter, der DI-Container sollte mehr Funktionen als der Services Locator haben, aber das Wesentliche dieser Veröffentlichung ist, dass selbst der anspruchsvollste DI-Container problemlos als Services Locator verwendet werden kann, aber um den Services Locator zu verwenden als DI-Container - müssen Sie versuchen.
PS
Ich kann nicht anders, als Links zu Sergei Teplyakovs Programmierblog bereitzustellen - diese Themen werden dort sehr gut behandelt.