Die verherrlichte globale Variable - wird zu einer verherrlichten globalen Klasse. Einige sagen, objektorientiertes Design zu brechen.
Geben Sie mir andere Szenarien als den guten alten Logger, in denen es sinnvoll ist, den Singleton zu verwenden.
Die verherrlichte globale Variable - wird zu einer verherrlichten globalen Klasse. Einige sagen, objektorientiertes Design zu brechen.
Geben Sie mir andere Szenarien als den guten alten Logger, in denen es sinnvoll ist, den Singleton zu verwenden.
Antworten:
Auf meiner Suche nach der Wahrheit entdeckte ich, dass es tatsächlich nur sehr wenige "akzeptable" Gründe gibt, einen Singleton zu verwenden.
Ein Grund, der im Internet immer wieder auftaucht, ist der einer "Protokollierungs" -Klasse (die Sie erwähnt haben). In diesem Fall kann ein Singleton anstelle einer einzelnen Instanz einer Klasse verwendet werden, da eine Protokollierungsklasse normalerweise von jeder Klasse in einem Projekt immer wieder ad nauseam verwendet werden muss. Wenn jede Klasse diese Protokollierungsklasse verwendet, wird die Abhängigkeitsinjektion umständlich.
Die Protokollierung ist ein spezielles Beispiel für einen "akzeptablen" Singleton, da sie die Ausführung Ihres Codes nicht beeinflusst. Deaktivieren Sie die Protokollierung, die Codeausführung bleibt unverändert. Aktivieren Sie es genauso. Misko bringt es in Root Cause of Singletons folgendermaßen auf den Punkt : "Die Informationen hier fließen in eine Richtung: Von Ihrer Anwendung in den Logger. Obwohl Logger einen globalen Status haben, sind Logger akzeptabel, da keine Informationen von Loggern in Ihre Anwendung fließen."
Ich bin mir sicher, dass es auch andere triftige Gründe gibt. Alex Miller spricht in " Patterns I Hate " davon, dass Service Locators und clientseitige Benutzeroberflächen möglicherweise auch "akzeptable" Entscheidungen sind.
Lesen Sie mehr bei Singleton Ich liebe dich, aber du bringst mich runter.
Ein Singleton-Kandidat muss drei Anforderungen erfüllen:
Wenn Ihr vorgeschlagener Singleton nur eine oder zwei dieser Anforderungen hat, ist eine Neugestaltung fast immer die richtige Option.
Beispielsweise ist es unwahrscheinlich, dass ein Druckerspooler von mehr als einer Stelle (dem Menü Drucken) aufgerufen wird, sodass Sie Mutexe verwenden können, um das Problem des gleichzeitigen Zugriffs zu lösen.
Ein einfacher Logger ist das offensichtlichste Beispiel für einen möglicherweise gültigen Singleton. Dies kann sich jedoch bei komplexeren Protokollierungsschemata ändern.
Lesen von Konfigurationsdateien, die nur zum Startzeitpunkt gelesen werden sollten, und Einkapseln in einen Singleton.
Properties.Settings.Default
in .NET.
Sie verwenden einen Singleton, wenn Sie eine gemeinsam genutzte Ressource verwalten müssen. Zum Beispiel ein Druckerspooler. Ihre Anwendung sollte nur eine einzige Instanz des Spoolers haben, um widersprüchliche Anforderungen für dieselbe Ressource zu vermeiden.
Oder eine Datenbankverbindung oder ein Dateimanager usw.
Schreibgeschützte Singletons, die einen globalen Status (Benutzersprache, Hilfedateipfad, Anwendungspfad) speichern, sind sinnvoll. Seien Sie vorsichtig, wenn Sie Singletons zur Steuerung der Geschäftslogik verwenden - Single ist fast immer mehrfach
Verwalten einer Verbindung (oder eines Pools von Verbindungen) zu einer Datenbank.
Ich würde es auch verwenden, um Informationen in externen Konfigurationsdateien abzurufen und zu speichern.
Eine Möglichkeit, einen Singleton zu verwenden, besteht darin, eine Instanz abzudecken, in der ein einzelner "Broker" den Zugriff auf eine Ressource steuern muss. Singletons eignen sich gut für Logger, da sie beispielsweise den Zugriff auf eine Datei vermitteln, in die nur exklusiv geschrieben werden kann. Für etwas wie die Protokollierung bieten sie eine Möglichkeit, die Schreibvorgänge in eine Protokolldatei zu abstrahieren - Sie können einen Caching-Mechanismus in Ihren Singleton usw. einbinden.
Denken Sie auch an eine Situation, in der Sie eine Anwendung mit vielen Fenstern / Threads / usw. haben, die jedoch einen einzigen Kommunikationspunkt benötigt. Ich habe einmal einen verwendet, um Jobs zu steuern, bei denen meine Anwendung gestartet werden soll. Der Singleton war dafür verantwortlich, die Jobs zu serialisieren und ihren Status jedem anderen Teil des Programms anzuzeigen, der interessiert war. In einem solchen Szenario können Sie einen Singleton als eine Art "Server" -Klasse betrachten, die in Ihrer Anwendung ausgeführt wird ... HTH
Ein Singleton sollte verwendet werden, wenn der Zugriff auf eine Ressource verwaltet wird, die von der gesamten Anwendung gemeinsam genutzt wird, und es wäre destruktiv, wenn möglicherweise mehrere Instanzen derselben Klasse vorhanden wären. Die Sicherstellung, dass der Zugriff auf gemeinsam genutzte Ressourcen threadsicher ist, ist ein sehr gutes Beispiel dafür, wo diese Art von Muster von entscheidender Bedeutung sein kann.
Wenn Sie Singletons verwenden, sollten Sie sicherstellen, dass Sie nicht versehentlich Abhängigkeiten verbergen. Im Idealfall werden die Singletons (wie die meisten statischen Variablen in einer Anwendung) während der Ausführung Ihres Initialisierungscodes für die Anwendung eingerichtet (static void Main () für ausführbare C # -Dateien, static void main () für ausführbare Java-Dateien) und dann an übergeben alle anderen instanziierten Klassen, die dies erfordern. Dies hilft Ihnen, die Testbarkeit aufrechtzuerhalten.
Ich denke, die Verwendung von Singleton kann als die gleiche wie die Viele-zu-Eins-Beziehung in Datenbanken angesehen werden. Wenn Sie viele verschiedene Teile Ihres Codes haben, die mit einer einzelnen Instanz eines Objekts arbeiten müssen, ist es sinnvoll, Singletons zu verwenden.
Ein praktisches Beispiel für einen Singleton finden Sie in Test :: Builder , der Klasse, die nahezu jedes moderne Perl-Testmodul unterstützt. Der Test :: Builder-Singleton speichert und vermittelt den Status und den Verlauf des Testprozesses (historische Testergebnisse, zählt die Anzahl der ausgeführten Tests) sowie Dinge wie die Testausgabe. Diese sind alle erforderlich, um mehrere Testmodule zu koordinieren, die von verschiedenen Autoren geschrieben wurden, um in einem einzigen Testskript zusammenzuarbeiten.
Die Geschichte von Test :: Builders Singleton ist lehrreich. Wenn new()
Sie anrufen, erhalten Sie immer das gleiche Objekt. Zunächst wurden alle Daten als Klassenvariablen mit nichts im Objekt selbst gespeichert. Dies funktionierte, bis ich Test :: Builder mit sich selbst testen wollte. Dann brauchte ich zwei Test :: Builder-Objekte, eines als Dummy, um sein Verhalten und seine Ausgabe zu erfassen und zu testen, und eines, um das eigentliche Testobjekt zu sein. Zu diesem Zeitpunkt wurde Test :: Builder in ein reales Objekt umgestaltet. Das Singleton-Objekt wurde als Klassendaten gespeichert und gab new()
es immer zurück. create()
wurde hinzugefügt, um ein neues Objekt zu erstellen und das Testen zu ermöglichen.
Derzeit möchten Benutzer einige Verhaltensweisen von Test :: Builder in ihrem eigenen Modul ändern, andere jedoch in Ruhe lassen, während der Testverlauf für alle Testmodule gleich bleibt. Was jetzt passiert, ist, dass das monolithische Test :: Builder-Objekt in kleinere Teile (Verlauf, Ausgabe, Format ...) zerlegt wird, die von einer Test :: Builder-Instanz zusammengetragen werden. Jetzt muss Test :: Builder kein Singleton mehr sein. Seine Komponenten können wie die Geschichte sein. Dies drückt die unflexible Notwendigkeit eines Singletons auf eine Ebene. Es gibt dem Benutzer mehr Flexibilität beim Mischen und Anpassen von Teilen. Die kleineren Singleton-Objekte können jetzt nur noch Daten speichern, wobei ihre enthaltenen Objekte entscheiden, wie sie verwendet werden sollen. Es ermöglicht sogar einer Nicht-Test :: Builder-Klasse, mitzuspielen, indem der Test :: Builder-Verlauf verwendet und Singletons ausgegeben werden.
Es scheint ein Push-and-Pull zwischen der Koordination von Daten und der Flexibilität des Verhaltens zu geben, das gemindert werden kann, indem der Singleton nur gemeinsam genutzte Daten mit möglichst wenig Verhalten umgibt, um die Datenintegrität sicherzustellen.
Wenn Sie ein Konfigurationseigenschaftsobjekt entweder aus der Datenbank oder einer Datei laden, ist es hilfreich, es als Singleton zu haben. Es gibt keinen Grund, statische Daten, die sich während der Ausführung des Servers nicht ändern, erneut zu lesen.
Sie können Singleton verwenden, wenn Sie das Statusmuster implementieren (wie im GoF-Buch gezeigt). Dies liegt daran, dass die konkreten Statusklassen keinen eigenen Status haben und ihre Aktionen in Form einer Kontextklasse ausführen.
Sie können Abstract Factory auch zu einem Singleton machen.
setState()
, sich für die Entscheidung über die Richtlinie zur Erstellung des Staates verantwortlich zu machen . Es ist hilfreich, wenn Ihre Programmiersprache Vorlagen oder Generika unterstützt. Anstelle von Singleton können Sie auch das Monostate- Muster verwenden, bei dem beim Instanziieren eines Statusobjekts dasselbe globale / statische Statusobjekt wiederverwendet wird. Die Syntax zum Ändern des Status kann unverändert bleiben, da Ihre Benutzer nicht wissen müssen, dass der instanziierte Status ein Monostate ist.
Geteilte Ressourcen. Insbesondere in PHP eine Datenbankklasse, eine Vorlagenklasse und eine globale variable Depotklasse. Alle müssen von allen Modulen / Klassen gemeinsam genutzt werden, die im gesamten Code verwendet werden.
Es handelt sich um eine echte Objektverwendung -> Die Vorlagenklasse enthält die Seitenvorlage, die erstellt wird, und wird durch Module, die zur Seitenausgabe hinzugefügt werden, geformt, hinzugefügt und geändert. Es muss als einzelne Instanz aufbewahrt werden, damit dies geschehen kann, und das Gleiche gilt für Datenbanken. Mit einem gemeinsam genutzten Datenbank-Singleton können die Klassen aller Module auf Abfragen zugreifen und diese abrufen, ohne sie erneut ausführen zu müssen.
Ein Singleton mit globalem Variablendepot bietet Ihnen ein globales, zuverlässiges und einfach zu verwendendes Variablendepot. Es räumt Ihren Code sehr auf. Stellen Sie sich vor, Sie haben alle Konfigurationswerte in einem Array in einem Singleton wie folgt:
$gb->config['hostname']
oder alle Sprachwerte in einem Array haben wie:
$gb->lang['ENTER_USER']
Am Ende der Ausführung des Codes für die Seite erhalten Sie beispielsweise eine jetzt ausgereifte:
$template
Singleton, a $gb
Singleton, in den das lang-Array zum Ersetzen eingefügt und alle Ausgaben geladen und bereit sind. Sie ersetzen sie einfach durch die Schlüssel, die jetzt im Seitenwert des ausgereiften Vorlagenobjekts vorhanden sind, und stellen sie dann dem Benutzer zur Verfügung.
Der große Vorteil davon ist, dass Sie JEDE Nachbearbeitung durchführen können, die Sie für alles mögen. Sie können alle Sprachwerte an Google Übersetzer oder einen anderen Übersetzungsdienst weiterleiten und sie zurückerhalten und an ihren Stellen ersetzen, z. B. übersetzt. Sie können auch Seitenstrukturen oder Inhaltszeichenfolgen nach Belieben ersetzen.
Es kann sehr pragmatisch sein, bestimmte Infrastrukturprobleme als Singletons oder globale Variablen zu konfigurieren. Mein Lieblingsbeispiel hierfür sind Dependency Injection-Frameworks, die Singletons verwenden, um als Verbindungspunkt zum Framework zu fungieren.
In diesem Fall sind Sie von der Infrastruktur abhängig, um die Verwendung der Bibliothek zu vereinfachen und unnötige Komplexität zu vermeiden.
Ich benutze es für ein Objekt, das Befehlszeilenparameter kapselt, wenn es um steckbare Module geht. Das Hauptprogramm kennt die Befehlszeilenparameter für geladene Module nicht (und weiß nicht immer, welche Module geladen werden). Beispiel: Hauptlasten A, die selbst keine Parameter benötigen (warum also ein zusätzlicher Zeiger / Referenz / was auch immer benötigt wird, ich bin mir nicht sicher - sieht nach Verschmutzung aus), laden dann die Module X, Y und Z. Zwei Von diesen, z. B. X und Z, benötigen (oder akzeptieren) Parameter, sodass sie den Befehlszeilen-Singleton zurückrufen, um ihm mitzuteilen, welche Parameter akzeptiert werden sollen, und zur Laufzeit zurückrufen, um herauszufinden, ob der Benutzer tatsächlich irgendwelche angegeben hat von ihnen.
In vielerlei Hinsicht würde ein Singleton für die Behandlung von CGI-Parametern ähnlich funktionieren, wenn Sie nur einen Prozess pro Abfrage verwenden (andere mod_ * -Methoden tun dies nicht, daher wäre es dort schlecht - daher das Argument, dass Sie es nicht tun sollten. Verwenden Sie keine Singletons in der mod_cgi-Welt, falls Sie auf mod_perl oder eine andere Welt portieren.
Vielleicht ein Beispiel mit Code.
Hier ist die ConcreteRegistry ein Singleton in einem Pokerspiel, das es den Verhaltensweisen bis zum Ende des Paketbaums ermöglicht, auf die wenigen Kernschnittstellen des Spiels (dh die Fassaden für das Modell, die Ansicht, den Controller, die Umgebung usw.) zuzugreifen:
http://www.edmundkirwan.com/servlet/fractal/cs1/frac-cs40.html
Ed.
1 - Ein Kommentar zur ersten Antwort:
Ich bin mit einer statischen Logger-Klasse nicht einverstanden. Dies kann für eine Implementierung praktisch sein, kann jedoch nicht für Komponententests ersetzt werden. Eine statische Klasse kann nicht durch ein Testdouble ersetzt werden. Wenn Sie keinen Unit-Test durchführen, wird das Problem hier nicht angezeigt.
2 - Ich versuche, keinen Singleton von Hand zu erstellen. Ich erstelle einfach ein einfaches Objekt mit Konstruktoren, mit denen ich Mitarbeiter in das Objekt einfügen kann. Wenn ich einen Singleton benötige, würde ich ein Abhängigkeits-Inyection-Framework (Spring.NET, Unity für .NET, Spring für Java) oder ein anderes verwenden.
ILogger logger = Logger.SingleInstance();
wo diese Methode statisch ist und es gibt eine statisch gespeicherte Instanz eines ILogger. Sie haben das Beispiel eines "Abhängigkeitsinjektions-Frameworks" verwendet. Fast alle DI-Container sind Singletons; Ihre Konfigurationen werden statisch definiert und letztendlich von einer einzigen Dienstanbieterschnittstelle aus abgerufen / gespeichert.