Alternativen zum Singleton-Muster


27

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

  1. 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).
  2. 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.


8
Abhängigkeit Injection ...
Falcon

1
@ Falcon: Abhängigkeitsinjektion ... was? DI unternimmt nichts, um dieses Problem zu lösen, bietet jedoch häufig eine praktische Möglichkeit, es auszublenden.
pdr

@ Falcon meinst du IoC Container? Auf diese Weise können Sie Abhängigkeiten in einer einzelnen Zeile auflösen. ZB Dependency.Resolve <IFoo> (); oder Dependency.Resolve <IFoo> (). DoSomething ();
CodeART

Dies ist eine ziemlich alte Frage, die bereits beantwortet wurde. Ich denke
TheSilverBullet

Antworten:


7

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.


17

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:

  • kurzlebige lokale Instanzen, z. B. für eine Fabrik: Typische Fabrikklassen haben, wenn überhaupt, nur minimale Mengen an Staat, und es gibt keinen wirklichen Grund, sie am Leben zu erhalten, nachdem sie ihren Zweck erfüllt haben; Der Aufwand für das Erstellen und Löschen von Klassen ist in 99% aller realen Szenarien nicht zu befürchten
  • Weitergeben einer Instanz, z. B. für einen Ressourcenmanager: Diese müssen eine lange Lebensdauer haben, da das Laden von Ressourcen teuer ist und Sie sie im Speicher behalten möchten, aber es gibt absolut keinen Grund, die Erstellung weiterer Instanzen zu verhindern - wer weiß, Vielleicht ist es sinnvoll, ein paar Monate später einen zweiten Ressourcenmanager zu haben ...

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.


3
Dies ist eine sehr gute Antwort, aber ich habe nicht wirklich gefragt, warum ich das Singleton-Muster verwenden soll / soll. Ich kenne die Probleme, die man mit dem globalen Staat haben kann. Wie auch immer, das Thema der Frage war, wie man eindeutige Objekte und dennoch eine Singleton-Muster-freie Implementierung hat.
Giorgio

3

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.


Meinen Sie, dass die Grundideenabhängigkeitsinjektion im Grunde der erste Ansatz ist, den ich oben skizziert habe (z. B. mithilfe einer Registrierung), und schließlich die Grundidee?
Giorgio

@ Giorgio: DI enthält immer eine solche Registrierung, aber der wichtigste Punkt, der es besser macht, ist, dass Sie, anstatt die Registrierung überall dort abzufragen, wo Sie ein Objekt benötigen, die zentralen Objekte haben, die transitiv gefüllt sind, wie ich oben schrieb.
Michael Borgwardt

@Giorgio: An einem konkreten Beispiel ist es einfacher zu verstehen: Nehmen wir an, Sie haben eine Java EE-App. Ihre Front-End-Logik befindet sich in einer Aktionsmethode einer JSF Managed Bean. Wenn der Benutzer eine Schaltfläche drückt, erstellt der JSF-Container eine Instanz der verwalteten Bean und ruft die Aktionsmethode auf. Zuvor betrachtet die DI-Komponente jedoch die Felder der Managed Bean-Klasse, und diejenigen mit einer bestimmten Annotation werden mit einem Verweis auf ein Service-Objekt gemäß der DI-Konfiguration gefüllt, und diese Service-Objekte haben dasselbe mit sich zu tun . Die Aktionsmethode kann dann die Dienste aufrufen.
Michael Borgwardt

Einige Leute (einschließlich Sie) lesen die Fragen nicht sorgfältig und verstehen falsch, was tatsächlich gefragt wird.
Giorgio

1
@ Giorgio: Nichts in Ihrer ursprünglichen Frage weist darauf hin, dass Sie bereits mit der Abhängigkeitsinjektion vertraut sind. Und siehe aktualisierte Antwort.
Michael Borgwardt

3

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:

GetSomeManager()->GetRootManager()->GetAnotherManager()->DoActualThingICareAbout()

Welchen Vorteil hat DI, abgesehen vom Verspotten, gegenüber der Verwendung eines Singletons? Das ist eine Frage, die mich sehr lange beschäftigt hat. Sind Registry- und Root-Manager nicht entweder als Singleton oder über DI implementiert? (In der Tat, der IoC - Container ist eine Registrierung, nicht wahr?)
Konrad Rudolph

1
@KonradRudolph, mit DI sind die Abhängigkeiten explizit und das Objekt macht keine Annahmen darüber, wie eine bestimmte Ressource bereitgestellt wird. Bei Singletons sind die Abhängigkeiten implizit und bei jeder Verwendung wird davon ausgegangen, dass es immer nur ein einzelnes solches Objekt geben wird.
Winston Ewert

@KonradRudolph, sind die anderen Methoden mit Singletons oder DI implementiert? Ja und Nein. Am Ende müssen Sie entweder Objekte übergeben oder Objekte im globalen Status speichern. Es gibt jedoch subtile Unterschiede in der Art und Weise, wie Sie dies tun. Die drei oben genannten Versionen vermeiden die Verwendung des globalen Status. Die Art und Weise, wie der Endcode die Referenzen erhält, ist jedoch unterschiedlich.
Winston Ewert

Die Verwendung von Singletons setzt kein einzelnes Objekt voraus, wenn der Singleton eine Schnittstelle implementiert (oder auch nicht) und Sie die Singleton-Instanz an Methoden übergeben, anstatt Singleton::instanceüberall im Client-Code abzufragen .
Konrad Rudolph

@KonradRudolph, dann verwenden Sie wirklich einen hybriden Singleton / DI-Ansatz. Meiner puristischen Seite nach sollten Sie sich für einen reinen DI-Ansatz entscheiden. Die meisten Probleme mit Singleton gelten dort jedoch nicht wirklich.
Winston Ewert

1

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.

class Factory
{
public:
    static Object* createObject(...);
};

Object* obj = Factory::createObject(...);

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.


4
Persönlich sehe ich dies nur als eine andere Implementierung des Singleton-Musters. In diesem Fall ist die Singleton-Instanz die Klasse selbst. Speziell: Es hat alle die gleichen Nachteile wie ein "echter" Singleton.
Joachim Sauer

@ Randall Cook: Ich denke, Ihre Antwort fängt meinen Standpunkt ein. Ich habe sehr oft gelesen, dass der Singleton sehr sehr schlecht ist und unbedingt vermieden werden sollte. Ich stimme dem zu, denke aber andererseits, dass es technisch unmöglich ist, dies immer zu vermeiden. Wenn Sie "eines von etwas" benötigen, müssen Sie es irgendwo erstellen und auf irgendeine Weise darauf zugreifen.
Giorgio

1

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.


0

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):

// network info
class Session {
  // ...
};

// current user connection info
class CurrentUser {
  // ...
};

// configuration upon user
class UserConfiguration {
  // ...
};

// configuration upon P.C.,
// example: monitor resolution
class TerminalConfiguration {
  // ...
};

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):

// network info
class NetworkInfoClass {
  // ...
};

// current user connection info
class CurrentUserClass {
  // ...
};

// configuration upon user
// favorite options menues
class UserConfigurationClass {
  // ...
};

// configuration upon P.C.,
// example: monitor resolution
class TerminalConfigurationClass {
  // ...
};

// this class is instantiated as a singleton
class SessionClass {
  public: NetworkInfoClass NetworkInfo;
  public: CurrentUserClass CurrentUser;
  public: UserConfigurationClass UserConfiguration;
  public: TerminalConfigurationClass TerminalConfiguration;

  public: static SessionClass Instance();
};

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.


0

Ihre ursprünglichen Anforderungen:

Das allgemeine Szenario für mich ist das

  1. Ich benötige nur eine Instanz einer Klasse, entweder aus Optimierungsgründen (ich benötige nicht> mehrere Factory-Objekte) oder um den gemeinsamen Status zu teilen (z. B. die Factory weiß, wie viele> Instanzen von A noch erstellt werden können).
  2. Ich brauche eine Möglichkeit, an verschiedenen Stellen des Codes auf diese Instanz f von F zuzugreifen.

Stimmen Sie nicht mit der Definition eines Singletons überein (und was Sie später eher bezeichnen). Aus GoF (meine Version ist 1995) Seite 127:

Stellen Sie sicher, dass eine Klasse nur eine Instanz hat, und stellen Sie einen globalen Zugriffspunkt bereit.

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.


0

Wie wäre es mit IoC-Container? Mit einigem Nachdenken können Sie am Ende etwas erreichen, das wie folgt lautet:

Dependency.Resolve<IFoo>(); 

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:

Dependency.Resolve<IFoo>().DoSomething();

Statische Singleton-Getter-Methode könnte ersetzt werden durch:

var result = Dependency.Resolve<IFoo>().GetFoo();

Beispiel ist relevant für .NET, aber ich bin sicher, sehr ähnlich kann in anderen Sprachen erreicht werden.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.