it-swarm.dev

Wann ist Singleton angemessen?

Einige halten das Singleton Pattern immer für ein Anti-Pattern. Was denken Sie?

67
Fishtoaster

Die beiden Hauptkritikpunkte an Singletons fallen nach meinen Beobachtungen in zwei Lager:

  1. Singletons werden von weniger fähigen Programmierern missbraucht und missbraucht, sodass alles zu einem Singleton wird und Sie Code sehen, der mit Class :: get_instance () - Referenzen übersät ist. Im Allgemeinen gibt es nur eine oder zwei Ressourcen (wie beispielsweise eine Datenbankverbindung), die für die Verwendung des Singleton-Musters qualifiziert sind.
  2. Singletons sind im Wesentlichen statische Klassen, die auf einer oder mehreren statischen Methoden und Eigenschaften beruhen. Alle statischen Dinge stellen echte, greifbare Probleme dar, wenn Sie versuchen, Unit-Tests durchzuführen, da sie Sackgassen in Ihrem Code darstellen, die nicht verspottet oder gestoppt werden können. Wenn Sie eine Klasse testen, die auf einem Singleton (oder einer anderen statischen Methode oder Klasse) basiert, testen Sie daher 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 Zugriff auf sie zur Verwendung gewährt werden kann das Containerobjekt.

32
Noah Goodrich

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, dass sie in Ihren zuvor unveränderlichen Singletons keinen veränderlichen Status einführen.

Eine Klasse verfügt möglicherweise über einen Konstruktor, der nur eine Zeichenfolge akzeptiert. Sie glauben also, dass diese isoliert instanziiert wird und keine Nebenwirkungen hat. Im Stillen kommuniziert es jedoch mit einer Art öffentlichem, global verfügbarem Singleton-Objekt, sodass es bei jeder Instanziierung der Klasse unterschiedliche Daten enthält. 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äß zu testen, müssen Sie den globalen Status im Singleton mikroverwalten und kennen, um konsistente Testergebnisse zu erhalten.

42
Magnus Wolffelt

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

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

34
dsimcha

Warum benutzen die Leute es?

Ich habe eine ganze Reihe von Singletons in der Welt gesehen PHP. Ich erinnere mich an keinen Anwendungsfall, in dem ich das Muster für gerechtfertigt hielt. Aber ich glaube, ich habe eine Vorstellung von der Motivation, warum Leute haben es benutzt.

  1. Single Instanz.

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

    Dies ist eine vernünftige Anforderung, z. für die "Standard-Datenbankverbindung". Dies bedeutet nicht, dass Sie niemals eine zweite Datenbankverbindung erstellen werden, sondern dass Sie normalerweise mit der Standardverbindung arbeiten.

  2. Single Instanziierung.

    "Erlauben Sie nicht, dass Klasse C mehr als einmal instanziiert wird (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 gestaltet wird - z. durch Eliminieren von Nebenwirkungen aus dem Klassenkonstruktor. Oder sie können auf andere Weise gelöst werden. Es kann jedoch noch einige legitime Anwendungsfälle geben.

    Sie sollten auch darüber nachdenken, ob die Anforderung "nur eine" wirklich "eine pro Prozess" bedeutet. Z.B. Für die gleichzeitige Verwendung von Ressourcen lautet die Anforderung eher "eine pro gesamtes System, prozessübergreifend" und nicht "eine pro Prozess". Und für andere Dinge ist es eher ein "Anwendungskontext", und Sie haben zufällig nur einen Anwendungskontext pro Prozess.

    Andernfalls muss diese Annahme nicht durchgesetzt werden. Dies zu erzwingen bedeutet auch, dass Sie keine separate Instanz für Komponententests erstellen können.

  3. Globaler Zugriff.

    Dies ist nur dann legitim, wenn Sie nicht über die richtige Infrastruktur verfügen, um Objekte an den Ort weiterzuleiten, an dem sie verwendet werden. Dies könnte bedeuten, dass Ihr Framework oder Ihre Umgebung nicht funktioniert, aber es liegt möglicherweise nicht in Ihrer Macht, dies zu beheben.

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

  4. Faule Instanziierung.

    Dies ist kein notwendiger Bestandteil eines Singletons, aber es scheint der beliebteste Weg zu sein, sie zu implementieren. Aber während faule Instanziierung eine nette Sache ist, brauchen Sie nicht wirklich einen Singleton, um dies 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 genannten Problemen beißt dies mit dem Prinzip der Einzelverantwortung , weil die Klasse Kontrolle ihrer eigenen Instanziierung und ihres eigenen Lebenszyklus , zusätzlich zu den anderen Verantwortlichkeiten, die die Klasse hat bereits.

Fazit

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

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

  • Einzelne Instanz (aber keine einzelne Instanziierung)
  • Globaler Zugang (weil Sie mit einem Rahmen aus der Bronzezeit arbeiten)
  • Faule Instanziierung (weil es einfach schön ist, sie zu haben)

In diesem Fall können Sie also Folgendes tun:

  • 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 S :: getInstance () -Methode mit verzögerter Instanziierung, die die Klasse C für die Instanz verwendet.

  • Beseitigen Sie alle Nebenwirkungen aus dem Konstruktor von C. Fügen Sie diese Nebenwirkungen stattdessen 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 kleiner lokaler Dienstcontainer verwalten und die statische Instanz nur für den Container verwenden. S :: getContainer () gibt Ihnen also einen Service-Container mit verzögerter Instanz und Sie erhalten die anderen Objekte aus dem Container.

  • Vermeiden Sie es, die statische getInstance () aufzurufen, wo Sie können. Verwenden Sie stattdessen, wann immer möglich, die Abhängigkeitsinjektion. Insbesondere wenn Sie den Container-Ansatz mit mehreren voneinander abhängigen Objekten verwenden, sollte keines dieser Objekte S :: getContainer () aufrufen müssen.

  • Erstellen Sie optional eine Schnittstelle, die von Klasse C implementiert wird, und dokumentieren Sie damit den Rückgabewert von S :: getInstance ().

(Nennen wir das immer noch einen Singleton? Ich überlasse dies 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 Bedenken, Prinzip der Einzelverantwortung.

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

7
donquixote

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 werde ich es nur verwenden, wenn ich es fast überall in meinem Code verwenden muss und ich möchte kein Objekt als Parameter an jede Klasse oder Funktion übergeben, die es benötigt.

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

Wenn Sie es nicht verwenden möchten, lesen Sie die obigen 3 und ändern Sie sie in das Gegenteil.

1
Brian R. Bondy