Singletons sind eine gute Idee, gut für die Performance, einfach greifbar, zentral!
Das könnte man zunächst denken, letztendlich stelle ich aber immer wieder fest, dass es weniger Fälle gibt, in denen ein Singleton wirklich Sinn macht und kaum jemand wird einen solchen Fall in der Praxis erzeugen können.
Zunächst einmal der Fall, wo ein Singleton Sinn macht: Basis-Datentypen wie Integer, Bool, Text, ...
Diese Typen sind einmalig, es gibt zwar mehrere Werte zu den Datentypen, aber die Typen selbst gibt es nur genau einmal. Doch wer programmiert schon Datentypen? In einer Hochsprache sicher niemand (und ich meine auch wirklich nur primitive Typen!)
Ein beliebtes Beispiel für die zunächst sinnvoll erscheinende Verwendung des Singleton Pattern sind auch objektifizierte Datenbankobjekte. Das ist aber ein Trugschluss, wie man schmerzhaft bemerken wird, wenn man mehrere parallele Verbindungen braucht (z.B. "Zend_DB")
Probleme durch die Verwendung von Singleton:
- Quasi nicht testbar.
- Extrem verändertes Verhalten bei Umstellung zu nicht-Singleton.
- "Globale" Objekte, die überall und von überall verändert werden können und deren Zustand so kaum überwachbar ist.
- Wiederverwendbarkeit außer für den konkret zuerst bedachten Fall quasi = 0.
Was ist also besser als ein Singleton, bietet mehr Flexibilität, bessere Testbarkeit und ähnliche Verfügbarkeit?
Letztendlich ist ein möglicher Ansatz aus meiner Sicht eine statische oder (einzige!!) Singleton Factory (Okay, diese Art von Singleton können wir nur minimieren, nicht verhindern), die mithilfe einer Registry die zuvor genannten Objekte, wie beispielsweise die häufig benötigte Datenbankverbindung liefert.
Die Registry innerhalb der Factory ist selbstverständlich nur nötig, wenn auch wirklich das selbe Objekt nur einmal gebraucht und herumgereicht wird. Ansonsten reicht eine einfache Factory ohne Zustand, um eine möglichst einfache Instanziierung zu erreichen ohne alle Parameter selbst liefern zu müssen. Dies ist insbesondere in Verbindung mit der Dependency Injection der einzelnen gelieferten Objekte eine gute Idee.
So erreichen wir zumindest die Wiederverwendbarkeit aller gelieferten Objekte und schaffen die meisten Nachteile des Singleton aus der Welt.
Das Singleton-Verhalten der Factory sei der Tatsache geschuldet, dass wir damit unseren zentralen Zugriff behalten und das "böse Verhalten" auf eine Klasse minimieren, die zudem das Verhalten der einzelnen instrumentalisierten Objekte nicht manipuliert.
Ein Problem behalten wir allerdings: "Globale" Objekte. Schließlich kann noch jeder andere in der Factory und DEM jeweils gelieferten Objekt herumändern, was zu Seiteneffekten führen kann.
An dieser Stelle sehe ich nur 2 mögliche Lösungen:
- Mit dem Problem leben und sich an Regeln halten, also vorsichtig/sparsam mit den entsprechenden Objekten umgehen, zudem möglichst zustandsfreie Objekte dafür einsetzen.
- Dependency Injection intensiv ohne Registry verwenden und Abhängigkeiten somit explizit im Konstruktor übergeben, also die "Magie" der versteckten Kanäle ganz abschalten. Das ist vermutlich die "sauberste" Variante, kann aber zu schlechter Lesbarkeit des Codes und großen Konstruktoren führen. Außerdem erhalten wir nicht mehr "das selbe" Objekt, was vielleicht auch inhaltlich und fachlich gewünscht ist und zudem Performancevorteile bietet.
Wir können also nicht erwarten, dass wir einfach und zentral unser Objekt bekommen, uns andererseits dadurch aber keine Abhängigkeiten schaffen, sondern diese nur reduzieren.
Ich setze meist eine Mischung aus beiden Methoden ein, es schadet aber nie sich über die Folgen bewusst zu sein. Auch hier gibt es wohl nicht DIE Lösung, sondern nur eine sinnvolle Lösung und zu beachtende Grundsätze.