Wann ist Singleton angemessen? [geschlossen]

Antworten:

32

Die beiden Hauptkritikpunkte an Singletons lassen sich anhand meiner Beobachtungen in zwei Lager unterteilen:

  1. Singletons werden von weniger fähigen Programmierern missbraucht und missbraucht, und so wird alles zu einem Singleton, und Sie sehen Code, der mit Class :: get_instance () - Referenzen übersät ist. Im Allgemeinen gibt es nur eine oder zwei Ressourcen (wie zum Beispiel eine Datenbankverbindung), die für die Verwendung des Singleton-Musters geeignet sind.
  2. Singletons sind im Wesentlichen statische Klassen, die sich auf eine oder mehrere statische Methoden und Eigenschaften stützen. Alle statischen Dinge stellen reale, greifbare Probleme dar, wenn Sie versuchen, Unit-Tests durchzuführen, da sie Sackgassen in Ihrem Code darstellen, die nicht verspottet oder gefälscht werden können. Wenn Sie also eine Klasse testen, die auf einem Singleton (oder einer anderen statischen Methode oder Klasse) basiert, testen Sie nicht nur diese Klasse, sondern auch die statische Methode oder Klasse.

Infolgedessen besteht ein gängiger Ansatz darin, ein breites Containerobjekt zu erstellen, um eine einzelne Instanz dieser Klassen aufzunehmen, und nur das Containerobjekt ändert diese Klassentypen, während vielen anderen Klassen der Zugriff auf diese Klassen zur Verwendung gewährt werden kann das Containerobjekt.

Noah Goodrich
quelle
6
Ich möchte nur sagen, dass Sie mit JMockit ( code.google.com/p/jmockit ) statische Methoden in Java nachahmen können . Es ist ein sehr praktisches Werkzeug.
Michael K
3
Ein paar Gedanken: Der Container ist ein Singleton, damit Sie Singletons nicht loswerden. Wenn Sie eine Bibliothek mit einer öffentlichen API schreiben, können Sie den Benutzer Ihrer API nicht zwingen, Ihren Container zu verwenden. Wenn Sie in Ihrer Bibliothek einen globalen Ressourcenmanager benötigen (z. B. zum Schutz des Zugriffs auf eine einzelne physische Ressource), müssen Sie einen Singleton verwenden.
Scott Whitlock
2
@Scott Whitlock warum muss es ein Singleton sein? Nur weil es nur eine einzige Instanz gibt, ist es nicht so. Jede IoC-Bibliothek wird diese Art von Abhängigkeit verwalten ...
MattDavey
Diese Lösung schlug vor, es auch als Toolbox
cregox 13.11.13
41

Ich stimme zu, dass es ein Anti-Muster ist. Warum? Weil es Ihrem Code erlaubt, über seine Abhängigkeiten zu lügen, und Sie anderen Programmierern nicht vertrauen können, in Ihren vorher unveränderlichen Singletons keinen veränderlichen Zustand einzuführen.

Eine Klasse verfügt möglicherweise über einen Konstruktor, der nur eine Zeichenfolge akzeptiert. Sie denken also, dass er isoliert instanziiert wird und keine Nebenwirkungen hat. Im Hintergrund kommuniziert es jedoch mit einer Art öffentlichem, global verfügbarem Singleton-Objekt, sodass jedes Mal, wenn Sie die Klasse instanziieren, andere Daten enthalten. Dies ist ein großes Problem, nicht nur für Benutzer Ihrer API, sondern auch für die Testbarkeit des Codes. Um den Code ordnungsgemäß auf Unit-Tests zu testen, müssen Sie den globalen Status im Singleton mikroverwalten und berücksichtigen, um konsistente Testergebnisse zu erhalten.

Magnus Wolffelt
quelle
Ich wünschte, ich könnte mehr als einmal stimmen!
MattDavey
34

Das Singleton-Muster ist im Grunde nur eine träge initialisierte globale Variable. Globale Variablen werden allgemein und zu Recht als böse angesehen, da sie gespenstische Aktionen in einiger Entfernung zwischen scheinbar nicht zusammenhängenden Programmteilen ermöglichen. IMHO ist jedoch nichts Falsches an globalen Variablen, die als Teil der Initialisierungsroutine eines Programms (z. B. durch Lesen einer Konfigurationsdatei oder von Befehlszeilenargumenten) einmalig von einer Stelle aus festgelegt und danach als Konstanten behandelt werden. Eine solche Verwendung globaler Variablen unterscheidet sich nur im Buchstaben und nicht im Geiste von der Deklaration einer benannten Konstante zur Kompilierungszeit.

In ähnlicher Weise bin ich der Meinung, dass Singletons genau dann schlecht sind, wenn sie verwendet werden, um einen veränderlichen Zustand zwischen scheinbar nicht verwandten Teilen eines Programms zu vermitteln. Wenn sie keinen veränderlichen Status enthalten oder der veränderliche Status, den sie enthalten, vollständig eingekapselt ist, sodass Benutzer des Objekts selbst in einer Multithread-Umgebung nichts davon wissen müssen, liegt nichts an ihnen.

dsimcha
quelle
3
Mit anderen Worten, Sie sind gegen jede Verwendung einer Datenbank in einem Programm.
Kendall Helmstetter Gelner
Es gibt ein berühmtes Google Tech Talk Video, Ihre Meinung Echos youtube.com/watch?v=-FRm3VPhseI
Martin Yorkeren
2
@KendallHelmstetterGelner: Es gibt einen Unterschied zwischen Zustand und Sekundärspeicher. Es ist unwahrscheinlich, dass Sie eine ganze Datenbank im Speicher halten können.
Martin York
7

Warum benutzen die Leute es?

Ich habe eine ganze Reihe von Singletons in der PHP-Welt gesehen. Ich kann mich an keinen Anwendungsfall erinnern, bei dem ich das Muster als gerechtfertigt empfand. Aber ich glaube, ich habe eine Idee über die Motivation, warum die Leute es benutzt haben.

  1. Einzelne Instanz .

    "Verwenden Sie eine einzelne Instanz der Klasse C in der gesamten Anwendung."

    Dies ist eine sinnvolle Voraussetzung zB für die "Standard-Datenbankverbindung". Dies bedeutet nicht, dass Sie niemals eine zweite Datenbankverbindung herstellen werden, sondern nur, dass Sie normalerweise mit der Standardverbindung arbeiten.

  2. Einzel Instanziierung .

    Msgstr "Klasse C darf nicht mehr als einmal instanziiert werden (pro Prozess, pro Anforderung usw.)."

    Dies ist nur relevant, wenn das Instanziieren der Klasse Nebenwirkungen haben würde, die mit anderen Instanzen in Konflikt stehen.

    Oft können diese Konflikte vermieden werden, indem die Komponente neu entworfen wird - z. B. indem Nebenwirkungen aus dem Klassenkonstruktor eliminiert werden. Oder sie können auf andere Weise gelöst werden. Möglicherweise gibt es jedoch noch einige legitime Anwendungsfälle.

    Sie sollten sich auch überlegen, ob die "einzige" Anforderung wirklich "eine pro Prozess" bedeutet. Zum Beispiel für die gleichzeitige Verwendung von Ressourcen ist die Anforderung eher "eine pro gesamtem System, prozessübergreifend" und nicht "eine pro Prozess". Bei anderen Dingen handelt es sich eher um einen "Anwendungskontext", und Sie haben zufällig einen Anwendungskontext pro Prozess.

    Andernfalls besteht keine Notwendigkeit, diese Annahme durchzusetzen. Wenn Sie dies erzwingen, können Sie auch keine separate Instanz für Komponententests erstellen.

  3. Globaler Zugriff.

    Dies ist nur dann legitim, wenn Sie keine geeignete Infrastruktur haben, um Objekte an den Ort weiterzuleiten, an dem sie verwendet werden. Dies könnte bedeuten, dass Ihr Framework oder Ihre Umgebung nicht funktioniert, es liegt jedoch möglicherweise nicht in Ihren Zuständigkeiten, dies zu beheben.

    Der Preis ist enge Kopplung, versteckte Abhängigkeiten und alles, was am globalen Staat schlecht ist. Aber Sie leiden wahrscheinlich schon diese.

  4. Lazy Instantiierung.

    Dies ist kein notwendiger Teil eines Singletons, aber es scheint die beliebteste Methode zu sein, sie zu implementieren. Aber während es eine nette Sache ist, faul zu instanziieren, braucht man keinen Singleton, um das zu erreichen.

Typische Implementierung

Die typische Implementierung ist eine Klasse mit einem privaten Konstruktor und einer statischen Instanzvariablen sowie einer statischen Methode getInstance () mit verzögerter Instanziierung.

Zusätzlich zu den oben erwähnten Problemen spricht dies für das Prinzip der einzelnen Verantwortung , da die Klasse zusätzlich zu den anderen Verantwortlichkeiten, die die Klasse bereits hat, ihre eigene Instanziierung und ihren eigenen Lebenszyklus kontrolliert .

Fazit

In vielen Fällen können Sie das gleiche Ergebnis ohne Singleton und ohne globalen Status erzielen. Stattdessen sollten Sie die Abhängigkeitsinjektion verwenden und möglicherweise einen Abhängigkeitsinjektionscontainer in Betracht ziehen .

Es gibt jedoch Anwendungsfälle, in denen die folgenden gültigen Anforderungen bestehen:

  • Einzelinstanz (aber keine Einzelinstanziierung)
  • Globaler Zugriff (da Sie mit einem bronzezeitlichen Framework arbeiten)
  • Lazy Instantiierung (weil es einfach schön zu haben ist)

Also, hier ist was Sie in diesem Fall tun können:

  • Erstellen Sie mit einem öffentlichen Konstruktor eine Klasse C, die Sie instanziieren möchten.

  • Erstellen Sie eine separate Klasse S mit einer statischen Instanzvariablen und einer statischen Methode S :: getInstance () mit verzögerter Instanziierung, die Klasse C für die Instanz verwendet.

  • Beseitigen Sie alle Nebenwirkungen aus dem Konstruktor von C. Fügen Sie stattdessen diese Nebenwirkungen in die Methode S :: getInstance () ein.

  • Wenn Sie mehr als eine Klasse mit den oben genannten Anforderungen haben, können Sie die Klasseninstanzen mit einem kleinen lokalen Dienstcontainer verwalten und die statische Instanz nur für den Container verwenden. S :: getContainer () gibt Ihnen also einen Lazy-Instantiated-Service-Container und Sie erhalten die anderen Objekte aus dem Container.

  • Vermeiden Sie es, das statische getInstance () aufzurufen, wo Sie können. Verwenden Sie stattdessen nach Möglichkeit die Abhängigkeitsinjektion. Insbesondere wenn Sie den Container-Ansatz für mehrere voneinander abhängige Objekte verwenden, sollte keines dieser Objekte S :: getContainer () aufrufen müssen.

  • Optional können Sie eine von Klasse C implementierte Schnittstelle erstellen und damit den Rückgabewert von S :: getInstance () dokumentieren.

(Nennen wir das immer noch einen Singleton? Ich überlasse das dem Kommentarbereich.)

Leistungen:

  • Sie können eine separate Instanz von C für Komponententests erstellen, ohne einen globalen Status zu berühren.

  • Das Instanzmanagement ist von der Klasse selbst getrennt -> Trennung von Anliegen, Prinzip der Einzelverantwortung.

  • Es ist ziemlich einfach, S :: getInstance () eine andere Klasse für die Instanz verwenden zu lassen oder sogar dynamisch zu bestimmen, welche Klasse verwendet werden soll.

donquixote
quelle
1

Persönlich verwende ich Singletons, wenn ich 1, 2 oder 3 oder eine begrenzte Anzahl von Objekten für die jeweilige Klasse benötige. Oder ich möchte dem Benutzer meiner Klasse mitteilen, dass nicht mehrere Instanzen meiner Klasse erstellt werden sollen, damit sie ordnungsgemäß funktioniert.

Außerdem verwende ich es nur, wenn ich es fast überall in meinem Code verwenden muss und ich kein Objekt als Parameter an jede Klasse oder Funktion übergeben möchte, die es benötigt.

Außerdem werde ich einen Singleton nur verwenden, wenn er die referentielle Transparenz einer anderen Funktion nicht beeinträchtigt. Dies bedeutet, dass bei bestimmten Eingaben immer die gleiche Ausgabe erfolgt. Dh ich benutze es nicht für den globalen Staat. Es sei denn, möglicherweise wird dieser globale Status einmal initialisiert und nie geändert.

Wenn Sie es nicht verwenden möchten, lesen Sie die obigen 3 und stellen Sie sie auf das Gegenteil um.

Brian R. Bondy
quelle
3
-0 aber ich fast -1. Ich würde es lieber überall in meinem Code weitergeben, anstatt ein Singleton zu verwenden, ABER wenn ich wirklich faul bin, kann ich das tun. Ich würde stattdessen globale Variablen verwenden, wenn ich in der Lage bin oder statische Mitglieder. Globale Vars sind (für mich) wirklich viel besser als Singletons. Globals nicht lügen