Abhängigkeitsinjektion vs Service Locator

Foto von John Carlisle auf Unsplash

Viele Entwickler bemerken den Unterschied zwischen der Abhängigkeitsinjektion und den Entwurfsmustern des Service-Locators nicht. Ja, beide versuchen, dasselbe Problem zu lösen - die Entkopplung zu verstärken. Wir alle wissen, welchen Nutzen dies für das Skalieren, Testen oder einfache Lesen des Codes hat.

Woher weiß ich jedoch, wann die Abhängigkeitsinjektion und wann der Service Locator verwendet werden muss? Ich würde sagen, wir sollten beide verwenden, wenn es angebracht ist.

Wenn ich gebeten würde, ein einzelnes Verb zu wählen, das das Abhängigkeitsinjektionsmuster am besten beschreibt, würde ich "geben" wählen. Wenn Sie darüber nachdenken, erreichen wir genau das mit der Abhängigkeitsinjektion. Wir geben einem Objekt einfach Objekte.

$ window = neues Fenster ();
$ door = new Door ();
$ house = neues Haus ($ window, $ door);

In diesem Fall übergeben wir das Fensterobjekt und das Türobjekt an das Hausobjekt.

Auf der anderen Seite würde ich, wenn ich gebeten würde, das Service Locator-Muster mit einem einzigen Verb zu beschreiben, sagen: "nehmen". Denken Sie einfach darüber nach. Dies tun wir, wenn wir einen Service-Locator verwenden. Wir nehmen Objekte von einem Objekt.

$ house = $ serviceLocator-> get (House :: class);

Wie Sie sehen, entnehmen wir das Hausobjekt dem Service Locator.

In vielen Fällen arbeiten die Abhängigkeitsinjektion und der Service-Locator als eine Einheit. Wir sehen es vielleicht nicht, wenn die Dinge automatisch injiziert werden, aber hinter den Kulissen verlassen sich die Implementierungen der Abhängigkeitsinjektion auf Service-Locators, um tatsächliche Abhängigkeiten abzurufen.

So könnte es irgendwo im Code aussehen:

foreach ($ dependencies als $ dependency) {
    $ instance [] = $ this-> container-> get ($ dependency);
}
return $ this-> resolve ($ class, $ instance);

Ein Service Locator ist ziemlich einfach zu implementieren. Sie müssen lediglich die Möglichkeit haben, eine angeforderte Instanz nach Name oder ID abzurufen und zu überprüfen, ob die angeforderte Instanz tatsächlich vorhanden ist. Beachten Sie, dass ein Service-Locator oft als Container bezeichnet wird. Beide Dinge sind gleich. Beide sind dafür gedacht, Instanzen oder Dienste bereitzustellen, wie auch immer Sie sie nennen möchten. Natürlich gibt es einen Unterschied zwischen einem Service und einer Instanz, wenn es um Begriffe und Zwecke geht, aber aus technischer Sicht handelt es sich bei allen um Instanzen bestimmter Klassen.

Hier ist eine einfache Version eines Service-Locators (auch Container genannt):

class ServiceLocator {

    private $ services = [];

    public function get (string $ id):? object {
        $ this-> services zurückgeben [$ id] ?? Null;
    }


    public function has (string $ id): bool {
        return isset ($ this-> services [$ id]);
    }

    öffentliches Funktionsregister (String $ id, Objekt $ service): void {
        $ this-> services [$ id] = $ service;
    }
}
$ serviceLocator = neuer ServiceLocator ();
$ serviceLocator-> register ('Haus', neues Haus ());
// irgendwo anders

if ($ serviceLocator-> has ('house')) {
    $ house = $ serviceLocator-> get ('house');
}

Außerdem habe ich die Möglichkeit zur Registrierung von Diensten bereitgestellt.

Die Implementierungen der Abhängigkeitsinjektion injizieren normalerweise Abhängigkeiten automatisch in Objekte. Alles, was Sie brauchen, ist ein bisschen Konfiguration und das ist alles. Dies ist in vielen Fällen sehr praktisch, aber in einigen Fällen müssen Sie den Service Locator verwenden, um einen Deadlock zu vermeiden. Dies kann passieren, wenn zwei Instanzen über einen Konstruktor voneinander abhängig sind. Hier ist ein Beispiel:

Klasse a {
    
    public function __construct (B $ b)
    {
        //
    }
}

Klasse b {
    public function __construct (A $ a)
    {
        //
    }
}
$ a = neues A (...); // Wir brauchen B!
$ b = neues B (...); // Wir brauchen ein!

Wie Sie sehen, können wir keinen der Dienste lösen, da sie voneinander abhängig sind. Der Dienst A kann nicht aufgelöst werden, da der Dienst B erforderlich ist, und der Dienst B kann nicht aufgelöst werden, weil der Dienst A erforderlich ist. Das heißt, wir sollten einen der Services an dem Punkt aus dem Service-Locator entnehmen, an dem der Service tatsächlich benötigt wird, und nicht im Konstruktor.

Alles in allem

Ich hoffe, dieser Artikel hat einige Dinge für Sie geklärt und Sie haben es genossen, ihn zu lesen. Wenn nicht, dann musst du schon der klügste Programmierer da draußen sein ;-)