Wenn Singletons schlecht sind, warum ist dann ein Service Container gut?

90

Wir alle wissen, wie schlecht Singletons sind, weil sie Abhängigkeiten verbergen und aus anderen Gründen .

In einem Framework kann es jedoch viele Objekte geben, die nur einmal instanziiert und von überall aufgerufen werden müssen (Logger, Datenbank usw.).

Um dieses Problem zu lösen, wurde mir gesagt, dass ich einen sogenannten "Objects Manager" (oder Service Container wie Symfony) verwenden soll, der jeden Verweis auf Services (Logger usw.) intern speichert.

Aber warum ist ein Dienstleister nicht so schlecht wie ein reiner Singleton?

Der Dienstanbieter verbirgt auch Abhängigkeiten und schließt nur die Erstellung der ersten Beziehung ab. Ich habe wirklich Probleme zu verstehen, warum wir einen Dienstanbieter anstelle von Singletons verwenden sollten.

PS. Ich weiß, dass ich DI verwenden sollte, um Abhängigkeiten nicht zu verbergen (wie von Misko angegeben).

Hinzufügen

Ich würde hinzufügen: Heutzutage sind Singletons nicht so böse, der Schöpfer von PHPUnit hat es hier erklärt:

DI + Singleton löst das Problem:

<?php
class Client {

    public function doSomething(Singleton $singleton = NULL){

        if ($singleton === NULL) {
            $singleton = Singleton::getInstance();
        }

        // ...
    }
}
?>

Das ist ziemlich klug, auch wenn dies nicht alle Probleme löst.

Gibt es außer DI und Service Container eine gute akzeptable Lösung für den Zugriff auf diese Hilfsobjekte?

dynamisch
quelle
2
@yes Ihre Bearbeitung macht falsche Annahmen. Sebastian schlägt in keiner Weise vor, dass das Code-Snippet die Verwendung von Singleons weniger problematisch macht. Es ist nur eine Möglichkeit, Code zu erstellen, der sonst nicht testbarer wäre. Aber es ist immer noch problematischer Code. In der Tat bemerkt er ausdrücklich: "Nur weil Sie können, heißt das nicht, dass Sie sollten". Die richtige Lösung wäre immer noch, Singletons überhaupt nicht zu verwenden.
Gordon
3
@yes folgt dem SOLID-Prinzip.
Gordon
19
Ich bestreite die Behauptung, dass Singletons schlecht sind. Sie können missbraucht werden, ja, aber auch jedes Werkzeug. Ein Skalpell kann verwendet werden, um ein Leben zu retten oder es zu beenden. Eine Kettensäge kann Wälder roden, um Buschbrände zu vermeiden, oder sie kann einen beträchtlichen Teil Ihres Arms abhacken, wenn Sie nicht wissen, was Sie tun. Lernen Sie Ihre Werkzeuge mit Bedacht zu verwenden und nicht behandeln Beratung als Evangelium - auf diese Weise liegt die unthinking Geist.
Paxdiablo
4
@paxdiablo aber sie sind schlecht. Singletons verletzen SRP, OCP und DIP. Sie führen einen globalen Status und eine enge Kopplung in Ihre Anwendung ein und lassen Ihre API über ihre Abhängigkeiten lügen. All dies wirkt sich negativ auf die Wartbarkeit, Lesbarkeit und Testbarkeit Ihres Codes aus. Es mag seltene Fälle geben, in denen diese Nachteile die kleinen Vorteile überwiegen, aber ich würde argumentieren, dass Sie in 99% keinen Singleton benötigen. Besonders in PHP, wo Singletons sowieso nur für die Anfrage einzigartig sind und es einfach ist, Collaborator-Diagramme aus einem Builder zusammenzustellen.
Gordon
5
Nein, das glaube ich nicht. Ein Tool ist ein Mittel, um eine Funktion auszuführen, normalerweise indem es irgendwie einfacher gemacht wird, obwohl einige (Emacs?) Die seltene Unterscheidung haben, es schwieriger zu machen :-) In dieser Hinsicht unterscheidet sich ein Singleton nicht von einem ausgeglichenen Baum oder einem Compiler . Wenn Sie nur eine Kopie eines Objekts sicherstellen müssen, führt dies ein Singleton aus. Ob es das gut macht kann diskutiert werden , aber ich glaube nicht , können Sie argumentieren , dass es nicht es überhaupt nicht tun. Und es gibt möglicherweise bessere Möglichkeiten, z. B. eine Kettensäge, die schneller als eine Handsäge ist, oder eine Nagelpistole gegen einen Hammer. Das macht die Handsäge / den Hammer nicht weniger zu einem Werkzeug.
Paxdiablo

Antworten:

75

Service Locator ist sozusagen nur das kleinere von zwei Übeln. Das "Geringere", das auf diese vier Unterschiede hinausläuft ( zumindest kann ich mir momentan keine anderen vorstellen ):

Prinzip der Einzelverantwortung

Service Container verstößt nicht wie Singleton gegen das Prinzip der Einzelverantwortung. Singletons kombinieren Objekterstellung und Geschäftslogik, während der Service Container streng für die Verwaltung der Objektlebenszyklen Ihrer Anwendung verantwortlich ist. In dieser Hinsicht ist Service Container besser.

Kupplung

Singletons werden aufgrund der statischen Methodenaufrufe normalerweise fest in Ihre Anwendung codiert, was zu eng gekoppelten und schwer zu verspottenden Abhängigkeiten führt in Ihrem Code führt. Der SL hingegen ist nur eine Klasse und kann injiziert werden. Während also alle Ihre Klassen davon abhängen, handelt es sich zumindest um eine lose gekoppelte Abhängigkeit. Wenn Sie den ServiceLocator nicht als Singleton selbst implementiert haben, ist dies etwas besser und auch einfacher zu testen.

Alle Klassen, die den ServiceLocator verwenden, hängen jetzt jedoch vom ServiceLocator ab, der auch eine Form der Kopplung darstellt. Dies kann durch die Verwendung einer Schnittstelle für den ServiceLocator verringert werden, sodass Sie nicht an eine konkrete ServiceLocator-Implementierung gebunden sind. Ihre Klassen hängen jedoch von der Existenz eines Locators ab, während die Nichtverwendung eines ServiceLocator die Wiederverwendung erheblich erhöht.

Versteckte Abhängigkeiten

Das Problem, Abhängigkeiten zu verbergen, besteht jedoch sehr. Wenn Sie den Locator nur in Ihre konsumierenden Klassen einfügen, kennen Sie keine Abhängigkeiten. Im Gegensatz zum Singleton instanziiert der SL normalerweise alle Abhängigkeiten, die hinter den Kulissen benötigt werden. Wenn Sie also einen Service abrufen, werden Sie nicht so abrufen Misko Hevery im CreditCard-Beispiel enden , z. B. müssen Sie nicht alle Abhängigkeiten der Abhängigkeiten von Hand instanziieren.

Das Abrufen der Abhängigkeiten aus der Instanz heraus verletzt ebenfalls Demeter-Gesetz , das besagt, dass Sie sich nicht mit Mitarbeitern befassen sollten. Eine Instanz sollte nur mit ihren unmittelbaren Mitarbeitern sprechen. Dies ist sowohl bei Singleton als auch bei ServiceLocator ein Problem.

Globaler Staat

Das Problem des globalen Status wird auch etwas gemildert, da beim Instanziieren eines neuen Service Locator zwischen den Tests auch alle zuvor erstellten Instanzen gelöscht werden (es sei denn, Sie haben den Fehler gemacht und sie in statischen Attributen im SL gespeichert). Das gilt natürlich nicht für einen globalen Zustand in Klassen, die von der SL verwaltet werden.

Weitere Informationen finden Sie unter Fowler zu Service Locator und Abhängigkeitsinjektion .


Ein Hinweis zu Ihrem Update und der verlinkte Artikel von Sebastian Bergmann zum Testen von Code, der Singletons verwendet : Sebastian legt in keiner Weise nahe, dass die vorgeschlagene Problemumgehung die Verwendung von Singleons weniger problematisch macht. Es ist nur eine Möglichkeit, Code zu erstellen, der sonst nicht testbarer wäre. Aber es ist immer noch problematischer Code. In der Tat bemerkt er ausdrücklich: "Nur weil Sie können, heißt das nicht, dass Sie sollten".

Gordon
quelle
Insbesondere die Testbarkeit sollte hier durchgesetzt werden. Sie können statische Methodenaufrufe nicht verspotten. Sie können jedoch Dienste verspotten, die über einen Konstruktor oder Setter injiziert wurden.
David
43

Das Service Locator-Muster ist ein Anti-Pattern. Das Problem des Offenlegens von Abhängigkeiten wird dadurch nicht gelöst (anhand der Definition einer Klasse können Sie nicht erkennen, um welche Abhängigkeiten es sich handelt, da sie nicht injiziert, sondern aus dem Service Locator herausgerissen werden).

Ihre Frage lautet also: Warum sind Service Locators gut? Meine Antwort lautet: Sie sind es nicht.

Vermeiden, vermeiden, vermeiden.

Jason
quelle
5
Sieht so aus, als wüssten Sie nichts über Schnittstellen. Die Klasse beschreibt nur die notwendige Schnittstelle in der Konstruktorsignatur - und das ist alles, was er wissen muss. Passed Service Locator sollte eine Schnittstelle implementieren, das ist alles. Und wenn IDE die Implementierung der Schnittstelle überprüft, ist es ziemlich einfach, Änderungen zu kontrollieren.
OZ_
4
@ yes123: Leute, die sagen, dass das falsch ist, und sie sind falsch, weil SL ein Anti-Muster ist. Ihre Frage lautet: "Warum sind SL gut?" Meine Antwort lautet: Sie sind es nicht.
Jason
4
Ich werde nicht darüber streiten, ob SL ein Anit-Muster ist oder nicht, aber ich werde sagen, dass es im Vergleich zu Singleton und Globals sehr viel weniger Übel ist. Sie können eine Klasse, die von einem Singleton abhängt, nicht testen, aber Sie können definitiv eine Klasse testen, die von einem SL abhängt (albiet können Sie das SL-Design so weit vermasseln, dass es nicht mehr funktioniert) ... Das ist es also wert unter Hinweis ...
ircmaxell
3
@Jason Sie müssen ein Objekt übergeben, das Interface implementiert - und es ist nur das, was Sie wissen müssen. Sie beschränken sich nur auf die Definition des Klassenkonstruktors und möchten alle Klassen (keine Schnittstellen) in den Konstruktor schreiben - das ist eine blöde Idee. Alles was Sie brauchen ist Schnittstelle. Sie können diese Klasse erfolgreich mit Mocks testen, Sie können das Verhalten leicht ändern, ohne den Code zu ändern, es gibt keine zusätzlichen Abhängigkeiten und Kopplungen - das ist alles (im Allgemeinen), was wir in Dependency Injection haben möchten.
OZ_
2
Sicher, ich werde einfach Datenbank, Logger, Datenträger, Vorlage, Cache und Benutzer in einem einzigen "Eingabe" -Objekt zusammenfassen. Sicher ist es einfacher zu erkennen, auf welche Abhängigkeiten mein Objekt angewiesen ist, als wenn ich einen Container verwendet hätte.
Mahn
4

Der Service-Container verbirgt Abhängigkeiten wie das Singleton-Muster. Vielleicht möchten Sie stattdessen die Verwendung von Abhängigkeitsinjektionscontainern vorschlagen, da diese alle Vorteile von Servicecontainern bietet, jedoch (soweit ich weiß) keine Nachteile von Servicecontainern aufweist.

Soweit ich weiß, besteht der einzige Unterschied zwischen beiden darin, dass im Servicecontainer der Servicecontainer das Objekt ist, das injiziert wird (wodurch Abhängigkeiten ausgeblendet werden). Wenn Sie DIC verwenden, injiziert der DIC die entsprechenden Abhängigkeiten für Sie. Die Klasse, die vom DIC verwaltet wird, ist sich der Tatsache, dass sie von einem DIC verwaltet wird, völlig bewusst, sodass Sie weniger Kopplung, klare Abhängigkeiten und glückliche Komponententests haben.

Dies ist eine gute Frage bei SO, die den Unterschied zwischen beiden erklärt: Was ist der Unterschied zwischen den Mustern Dependency Injection und Service Locator?

Rickchristie
quelle
"Der DIC fügt die entsprechenden Abhängigkeiten für Sie ein." Passiert dies nicht auch bei Singleton?
dynamische
5
@ yes123 - Wenn Sie einen Singleton verwenden, würden Sie ihn nicht injizieren. Meistens greifen Sie nur global darauf zu (das ist der Punkt von Singleton). Ich nehme an, wenn Sie sagen, dass wenn Sie den Singleton injizieren , er Abhängigkeiten nicht verbirgt, aber den ursprünglichen Zweck des Singleton-Musters irgendwie zunichte macht - Sie würden sich fragen, wenn ich nicht brauche, dass auf diese Klasse global zugegriffen wird, warum muss ich es Singleton machen?
Rickchristie
2

Da Sie Objekte im Service Container problemlos durch
1) Vererbung ersetzen können (Object Manager-Klasse kann vererbt und Methoden überschrieben werden)
2) Konfiguration ändern (im Fall von Symfony)

Und Singletons sind nicht nur wegen der hohen Kopplung schlecht, sondern auch, weil sie einzelne Tonnen sind. Es ist eine falsche Architektur für fast alle Arten von Objekten.

Mit 'pure' DI (in Konstruktoren) zahlen Sie einen sehr hohen Preis - alle Objekte sollten erstellt werden, bevor sie im Konstruktor übergeben werden. Dies bedeutet mehr genutzten Speicher und weniger Leistung. Außerdem kann nicht immer nur ein Objekt erstellt und im Konstruktor übergeben werden - es kann eine Abhängigkeitskette erstellt werden ... Mein Englisch ist nicht gut genug, um dies vollständig zu diskutieren. Lesen Sie es in der Symfony-Dokumentation.

OZ_
quelle
0

Ich versuche aus einem einfachen Grund, globale Konstanten und Singletons zu vermeiden. Es gibt Fälle, in denen möglicherweise APIs ausgeführt werden müssen.

Zum Beispiel habe ich Front-End und Admin. Innerhalb von admin möchte ich, dass sie sich als Benutzer anmelden können. Betrachten Sie den Code in admin.

$frontend = new Frontend();
$frontend->auth->login($_GET['user']);
$frontend->redirect('/');

Dies kann eine neue Datenbankverbindung, einen neuen Logger usw. für die Frontend-Initialisierung herstellen und prüfen, ob der Benutzer tatsächlich vorhanden, gültig usw. ist. Außerdem werden geeignete separate Cookie- und Ortungsdienste verwendet.

Meine Idee von Singleton ist: Sie können nicht zweimal dasselbe Objekt innerhalb des übergeordneten Objekts hinzufügen. Zum Beispiel

$logger1=$api->add('Logger');
$logger2=$api->add('Logger');

würde Sie mit einer einzelnen Instanz und beiden Variablen verlassen, die darauf zeigen.

Wenn Sie eine objektorientierte Entwicklung verwenden möchten, arbeiten Sie mit Objekten und nicht mit Klassen.

romaninsh
quelle
1
Ihre Methode ist es also, die $api Variable um Ihr Framework herum zu übergeben? Ich habe nicht genau verstanden, was du meinst. Auch wenn der Aufruf add('Logger')dieselbe Instanz zurückgibt, haben Sie im Grunde einen Service-Cotainer
dynamisch
ja das ist richtig. Ich bezeichne sie als "System Controller" und sie sollen die Funktionalität der API verbessern. In ähnlicher Weise würde das zweimalige Hinzufügen eines "überprüfbaren" Controllers zu einem Modell genauso funktionieren - erstellen Sie nur eine Instanz und nur einen Satz von Überwachungsfeldern.
Romaninsh