Ich habe unterschiedliche Meinungen über das Singleton-Muster gelesen. Einige vertreten die Ansicht, dass dies unter allen Umständen vermieden werden sollte und andere, dass es in bestimmten Situationen nützlich sein kann.
Eine Situation, in der ich Singletons verwende, besteht darin, dass ich eine Factory (sagen wir ein Objekt f vom Typ F) benötige, um Objekte einer bestimmten Klasse A zu erstellen. Die Factory wird mit einigen Konfigurationsparametern einmal erstellt und dann jedes Mal verwendet, wenn ein Objekt von Typ A wird instanziiert. Also ruft jeder Teil des Codes, der A instanziieren möchte, den Singleton f ab und erstellt die neue Instanz, z
F& f = F::instance();
boost::shared_ptr<A> a = f.createA();
Das allgemeine Szenario für mich ist das
- Ich benötige nur eine Instanz einer Klasse, entweder aus Optimierungsgründen (ich benötige nicht mehrere Factory-Objekte) oder zur Freigabe des gemeinsamen Status (z. B. die Factory weiß, wie viele Instanzen von A noch erstellt werden können).
- Ich brauche eine Möglichkeit, an verschiedenen Stellen des Codes auf diese Instanz f von F zuzugreifen.
Ich interessiere mich nicht für die Diskussion, ob dieses Muster gut oder schlecht ist, aber wenn ich die Verwendung eines Singleton vermeiden möchte, welches andere Muster kann ich verwenden?
Ich hatte die Idee, (1) das Factory-Objekt aus einer Registrierung zu holen oder (2) die Factory zu einem bestimmten Zeitpunkt während des Programmstarts zu erstellen und dann die Factory als Parameter weiterzugeben.
In Lösung (1) ist die Registrierung selbst ein Singleton, daher habe ich das Problem, dass kein Singleton ab Werk verwendet wird, auf die Registrierung verschoben.
Für den Fall (2), dass ich eine erste Quelle (Objekt) benötige, von der das Factory-Objekt stammt, befürchte ich, dass ich erneut auf einen anderen Singleton (das Objekt, das meine Factory-Instanz bereitstellt) zurückgreifen würde. Indem ich dieser Kette von Singletons folge, kann ich das Problem möglicherweise auf einen Singleton (die gesamte Anwendung) reduzieren, mit dem alle anderen Singletons direkt oder indirekt verwaltet werden.
Wäre diese letzte Option (die Verwendung eines ersten Singletons, der alle anderen eindeutigen Objekte erstellt und alle anderen Singletons an den richtigen Stellen einfügt) eine akzeptable Lösung? Ist dies die Lösung, die implizit vorgeschlagen wird, wenn davon abgeraten wird, Singletons zu verwenden, oder was sind andere Lösungen, z. B. im oben dargestellten Beispiel?
BEARBEITEN
Da ich denke, dass der Punkt meiner Frage von einigen missverstanden wurde, finden Sie hier weitere Informationen. Wie z. B. hier erläutert , kann das Wort Singleton (a) eine Klasse mit einem einzelnen Instanzobjekt und (b) ein Entwurfsmuster angeben, das zum Erstellen und Zugreifen auf ein solches Objekt verwendet wird.
Zur Verdeutlichung verwenden wir den Begriff eindeutiges Objekt für (a) und Singleton-Muster für (b). Ich weiß also, was das Singleton-Muster und die Abhängigkeitsinjektion sind (BTW, in letzter Zeit habe ich DI intensiv verwendet, um Instanzen des Singleton-Musters aus einem Code zu entfernen, an dem ich arbeite).
Mein Punkt ist, dass es immer notwendig sein wird, über das Singleton-Muster auf einige eindeutige Objekte zuzugreifen, es sei denn, der gesamte Objektgraph wird aus einem einzelnen Objekt instanziiert, das sich auf dem Stapel der Hauptmethode befindet.
Meine Frage ist, ob die vollständige Erstellung und Verkabelung von Objektgraphen von der Hauptmethode abhängt (z. B. durch ein leistungsfähiges DI-Framework, das das Muster selbst nicht verwendet). Dies ist die einzige singleton-musterfreie Lösung.
Antworten:
Ihre zweite Option ist ein guter Weg - es ist eine Art Abhängigkeitsinjektion, das Muster, das verwendet wird, um den Status in Ihrem Programm zu teilen, wenn Sie Singletons und globale Variablen vermeiden möchten.
Sie können nicht um die Tatsache herumkommen, dass etwas Ihre Fabrik schaffen muss. Wenn das etwas ist die Anwendung, so sei es. Der wichtige Punkt ist, dass es Ihrer Fabrik egal ist, von welchem Objekt sie erstellt wurde, und dass die Objekte, die die Fabrik erhalten, nicht davon abhängen sollten, woher die Fabrik stammt. Lassen Sie Ihre Objekte keinen Zeiger auf das Anwendungs-Singleton abrufen und fragen Sie nach der Factory. Lassen Sie Ihre Anwendung die Factory erstellen und geben Sie sie an die Objekte weiter, die sie benötigen.
quelle
Der Zweck eines Singletons besteht darin, zu erzwingen, dass in einem bestimmten Bereich immer nur eine Instanz existieren kann. Dies bedeutet, dass ein Singleton nützlich ist, wenn Sie starke Gründe haben, das Singleton-Verhalten zu erzwingen. In der Praxis ist dies jedoch selten der Fall, und Multiverarbeitung und Multithreading verschwimmen mit Sicherheit in der Bedeutung von "einzigartig" - ist es eine Instanz pro Maschine, pro Prozess, pro Thread und pro Anforderung? Und kümmert sich Ihre Singleton-Implementierung um die Rennbedingungen?
Anstelle von Singleton bevorzuge ich Folgendes:
Der Grund dafür ist, dass ein Singleton ein verschleierter globaler Status ist, was bedeutet, dass die gesamte Anwendung ein hohes Maß an Kopplung aufweist. Jeder Teil Ihres Codes kann die Singleton-Instanz mit minimalem Aufwand von jedem Ort aus abrufen. Wenn Sie lokale Objekte verwenden oder Instanzen weitergeben, haben Sie viel mehr Kontrolle, und Sie können Ihre Bereiche klein halten und Ihre Abhängigkeiten einschränken.
quelle
Die meisten Menschen (einschließlich Ihnen) verstehen völlig falsch, was das Singleton-Muster tatsächlich ist. Das Singleton-Muster bedeutet nur, dass eine Instanz einer Klasse vorhanden ist, und es gibt einen Mechanismus für Code in der gesamten Anwendung, um einen Verweis auf diese Instanz abzurufen.
Im GoF-Buch war die statische Factory-Methode, die einen Verweis auf ein statisches Feld zurückgibt, nur ein Beispiel dafür, wie dieser Mechanismus aussehen könnte, und eine Methode mit schwerwiegenden Nachteilen. Leider klammerten sich alle und ihr Hund an diesen Mechanismus und dachten, es sei das, worum es bei Singleton geht.
Die Alternativen, die Sie zitieren, sind in der Tat auch Singletons, nur mit einem anderen Mechanismus, um die Referenz zu erhalten. (2) führt eindeutig dazu, dass zu viel herumgereicht wird, es sei denn, Sie benötigen die Referenz nur an wenigen Stellen in der Nähe der Wurzel des Aufrufstapels. (1) klingt wie ein grobes Abhängigkeits-Injection-Framework - warum also nicht eines verwenden?
Der Vorteil ist, dass vorhandene DI-Frameworks flexibel, leistungsfähig und erprobt sind. Sie können viel mehr als nur Singletons verwalten. Um ihre Fähigkeiten voll auszuschöpfen, funktionieren sie jedoch am besten, wenn Sie Ihre Anwendung auf eine bestimmte Weise strukturieren, die nicht immer möglich ist: Im Idealfall gibt es zentrale Objekte, die über das DI-Framework erfasst werden und deren Abhängigkeiten transitiv ausgefüllt sind und sind dann ausgeführt.
Edit: Letztendlich hängt sowieso alles von der Hauptmethode ab. Aber Sie haben Recht: Die einzige Möglichkeit, die Verwendung von global / static vollständig zu vermeiden, besteht darin, alles von der Hauptmethode aus einzurichten. Beachten Sie, dass DI in Serverumgebungen am beliebtesten ist, in denen die Hauptmethode undurchsichtig ist und das Einrichten des Server- und Anwendungscodes im Wesentlichen aus Rückrufen besteht, die vom Servercode instanziiert und aufgerufen werden. Die meisten DI-Frameworks ermöglichen jedoch auch für "Sonderfälle" den direkten Zugriff auf ihre zentrale Registrierung, z. B. den ApplicationContext von Spring.
Das Beste, was die Leute bisher gemacht haben, ist eine clevere Kombination der beiden von Ihnen genannten Alternativen.
quelle
Es gibt einige Alternativen:
Abhängigkeitsspritze
Jedem Objekt wurden beim Erstellen Abhängigkeiten übergeben. In der Regel sind entweder ein Framework oder eine kleine Anzahl von Factory-Klassen für die Erstellung und Verkabelung der Objekte verantwortlich
Service Registry
Jedes Objekt wird einem Serviceregistrierungsobjekt übergeben. Es bietet Methoden zum Anfordern verschiedener Objekte, die unterschiedliche Dienste bereitstellen. Das Android-Framework verwendet dieses Muster
Root Manager
Es gibt ein einzelnes Objekt, das die Wurzel des Objektgraphen ist. Wenn Sie den Links von diesem Objekt aus folgen, können Sie schließlich jedes andere Objekt finden. Code sieht normalerweise so aus:
quelle
Singleton::instance
überall im Client-Code abzufragen .Wenn sich Ihre Anforderungen aus dem Singleton auf eine einzige Funktion reduzieren lassen, warum nicht einfach eine einfache Factory-Funktion verwenden? Globale Funktionen (in Ihrem Beispiel wahrscheinlich eine statische Methode der Klasse F) sind von Natur aus Singletons, deren Eindeutigkeit vom Compiler und Linker erzwungen wird.
Zugegebenermaßen bricht dies zusammen, wenn die Operation des Singletons nicht auf einen einzelnen Funktionsaufruf reduziert werden kann, obwohl Sie möglicherweise mit einer kleinen Menge verwandter Funktionen zurechtkommen.
Abgesehen davon machen die Punkte 1 und 2 in Ihrer Frage deutlich, dass Sie wirklich nur eines von etwas wollen. Abhängig von Ihrer Definition des Singleton-Musters verwenden Sie es entweder bereits oder sind sehr nah dran. Ich glaube nicht, dass Sie eines von etwas haben können, ohne dass es ein Singleton ist oder zumindest einem sehr nahe kommt. Es ist einfach zu nah an der Bedeutung von Singleton.
Wie Sie vorschlagen, müssen Sie irgendwann etwas haben, also besteht das Problem möglicherweise nicht darin, dass Sie nur eine Instanz von etwas haben, sondern dass Sie Schritte unternehmen, um dessen Missbrauch zu verhindern (oder zumindest zu verhindern oder zu minimieren). Es ist ein guter Anfang, so viel Status wie möglich aus dem "Singleton" in die Parameter zu verschieben. Das Eingrenzen der Schnittstelle zum Singleton hilft auch, da auf diese Weise weniger Möglichkeiten für Missbrauch bestehen. Manchmal muss man es einfach aufsaugen und den Singleton sehr robust machen, wie den Heap oder das Dateisystem.
quelle
Wenn Sie eine Multiparadigm-Sprache wie C ++ oder Python verwenden, ist eine Alternative zu einer Singleton-Klasse eine Reihe von Funktionen / Variablen, die in einen Namespace eingeschlossen sind.
Konzeptionell ergibt eine C ++ - Datei mit freien globalen Variablen, freien globalen Funktionen und statischen Variablen, die zum Ausblenden von Informationen verwendet werden und alle in einen Namespace eingeschlossen sind, fast den gleichen Effekt wie eine einzelne "Klasse".
Es bricht nur zusammen, wenn Sie Vererbung wünschen. Ich habe viele Singletons gesehen, die auf diese Weise besser dran gewesen wären.
quelle
Singletons Encapsulation
Fallszenario. Ihre Anwendung ist objektorientiert und erfordert 3 oder 4 spezifische Singletons, auch wenn Sie mehr Klassen haben.
Vor dem Beispiel (C ++ wie Pseudocode):
Eine Möglichkeit besteht darin, einige (globale) Singletons teilweise zu entfernen, indem alle in einem eindeutigen Singleton eingekapselt werden, der die anderen Singletons als Mitglieder hat.
Auf diese Weise haben wir anstelle des "Ansatzes mit mehreren Singletons im Vergleich zu keinem Singleton überhaupt" den "Einschluss aller Singletons in einem Ansatz".
Nach Beispiel (C ++ wie Pseudocode):
Beachten Sie, dass die Beispiele eher dem Pseudocode ähneln, und ignorieren Sie kleinere Fehler oder Syntaxfehler, und ziehen Sie die Lösung der Frage in Betracht.
Es ist auch noch etwas anderes zu beachten, da die verwendete Programmiersprache Einfluss darauf haben kann, wie Sie Ihre Singleton- oder Nicht-Singleton-Implementierung implementieren.
Prost.
quelle
Ihre ursprünglichen Anforderungen:
Stimmen Sie nicht mit der Definition eines Singletons überein (und was Sie später eher bezeichnen). Aus GoF (meine Version ist 1995) Seite 127:
Wenn Sie nur eine Instanz benötigen, können Sie dennoch weitere Instanzen erstellen .
Wenn Sie eine einzelne, global zugreifbare Instanz möchten, erstellen Sie eine einzelne, global zugreifbare Instanz . Für alles, was Sie tun, muss kein Muster angegeben werden. Und ja, einzelne global zugängliche Instanzen sind normalerweise schlecht. Wenn Sie ihnen einen Namen geben, werden sie nicht weniger schlecht .
Um globale Zugänglichkeit zu vermeiden, funktionieren die gängigen Ansätze „Instanz erstellen und weitergeben“ oder „zwei Objekte vom Eigentümer zusammenkleben lassen“ gut.
quelle
Wie wäre es mit IoC-Container? Mit einigem Nachdenken können Sie am Ende etwas erreichen, das wie folgt lautet:
Sie müssten angeben, welche Implementierung von
IFoo
bei einem Start verwendet werden soll, aber dies ist eine einmalige Konfiguration, die Sie später leicht ersetzen können. Die Lebensdauer einer aufgelösten Instanz kann normalerweise von einem IoC-Container gesteuert werden.Statische Singleton-Void-Methode könnte ersetzt werden durch:
Statische Singleton-Getter-Methode könnte ersetzt werden durch:
Beispiel ist relevant für .NET, aber ich bin sicher, sehr ähnlich kann in anderen Sprachen erreicht werden.
quelle