Patterns in der Softwareentwicklung: Vor- und Nachteile des Singleton-Musters
Das Singleton-Muster gilt als umstrittenes Design Pattern. Daher lohnt sich ein Blick auf die Vor- und Nachteile sowie mögliche Alternativen.
- Rainer Grimm
Das Singleton-Muster gilt als das am meisten umstrittene Entwurfsmuster aus dem Buch "Design Patterns: Elements of Reusable Object-Oriented Software" (Design Pattern). In meinem letzten Artikel "Patterns in der Softwareentwicklung: Das Singleton-Muster" habe ich das klassische Singleton-Muster und das sogenannte Meyers Singleton bereits vorgestellt. Nun möchte ich genauer auf die Vor- und Nachteile des Singletons eingehen – und etwaige Alternativen vorstellen.
Singleton: Ein Pattern oder ein Anti-Pattern?
"Thread-safe Initialization of a Singleton" ist mein bisher meistgelesener Artikel. Er wurde mehr als 300.000 Mal gelesen und ich habe viele Kommentare dazu erhalten. Daraufhin habe ich ĂĽber Twitter in der Community nachgefragt, wer das Singleton-Muster verwendet. Aus den etwa 150 Antworten auf meine Frage ergab sich folgendes Bild, das nicht so eindeutig ist, wie ich erwartet hatte: 59 Prozent nutzen das Singleton-Muster, aber 41 Prozent tun das nicht.
Die Kommentare, die ich zum Singleton-Muster erhalten habe, lassen sich in zwei Aussagen zusammenfassen:
- Ich benutze das Singleton nicht, weil es ein Anti-Pattern ist.
- Ich setze das Singleton nur gezielt ein.
Aus meiner täglichen Arbeit kenne ich zahlreiche Entwicklerinnen und Entwickler, die das Singleton-Muster häufig verwenden. Daher vermute ich, dass sich diese Gruppe in der Diskussion noch gar nicht zu Wort gemeldet hat. Wenn ich diese "schweigende" Fraktion in das Umfrageergebnis einbeziehe, dürften vermutlich bis zu 80 Prozent der Softwareentwickler das Singleton-Muster verwenden.
Lasst mich daher also tiefer in das Singleton-Muster eintauchen und seine Vor- und Nachteile analysieren.
Die Vor- und Nachteile des Singleton-Musters
Ich möchte positiv beginnen:
Vorteile
- Globaler Zugangspunkt
Ein Singleton ist ein getarntes globales Objekt, das einen globalen Zugriffspunkt anbietet. Als globales Objekt kann auf ein Singleton von überall im Programm zugegriffen werden, aber es kann nicht von überall aus geändert werden. Es kann nur innerhalb des Singletons geändert werden. Es ist also ein Mittel, um globale Objekte zu schützen.
- Einzigartiges Entitätsmodell
Reale Entitäten zu modellieren, hilft dabei, über das eigene Programm nachzudenken. In der Realität haben wir oft Singletons wie Einwohnermeldeämter, globale Zeitgeber oder Ämter für Identitäten. Durch das Modellieren erreichst du eine bessere Übereinstimmung zwischen deiner Programmabstraktion und der Realität. Diese Übereinstimmung hilft dir und deinem Kunden, das Programm besser zu verstehen.
Nachteile
- The Static Initialization Order Fiasco
In meinem letzten Artikel habe ich das Static Initialization Order Fiasco genau beschrieben. Es bedeutet, dass du keine Garantie dafĂĽr hast, in welcher Reihenfolge statische Elemente in verschiedenen Ăśbersetzungseinheiten initialisiert und destruiert werden. Die "Design Patterns: Elements of Reusable Object-Oriented Software" basierte Implementierung des Singelton Pattern besitzt dieses Problem, aber das Meyers Singleton ĂĽberwindet es.
- Concurrency
Das Meyers Singleton ĂĽberwindet auch das Concurrency-Problem der klassischen Singleton-Implementierung. Das Meyers Singleton basiert auf einer lokalen, statischen Variable. Seit C++98 werden statische Variable mit lokalem Geltungsbereich lazy und seit C++11 sogar thread-sicher initialisiert.
- Zu häufig verwendet
Das Singleton-Muster wurde oft verwendet, obwohl es unangemessen war, und ein einfaches Objekt die bessere Lösung gewesen wäre. Das lag vor allem daran, dass Softwareentwickler beweisen wollen, dass sie das klassische Entwurfsmuster verstanden haben und so das vermeintlich einfachste Pattern einsetzen. Ehrlich gesagt, können wir dem Singleton-Muster hier keinen Vorwurf machen, wenn es missbraucht wird.
- Versteckte Abhängigkeiten
Das ist mein Hauptkritikpunkt: Ein Singleton führt eine versteckte Abhängigkeit ein und unterläuft damit die Testbarkeit des Codes. Betrachten wir den folgenden Codeschnipsel:
void func() {
...
DataBase::getInstance().update("something");
...
}
Der Aufruf DataBase::getInstance().update("something")
erzeugt eine versteckte Abhängigkeit. Der Aufrufer der Funktion func
erhält einen Hinweis, dass intern eine Datenbank aufgerufen wird. Wie sehen die Folgen aus? Der Code ist keine Einheit mehr und kann nicht in Isolation getestet werden. Anstelle eines Unittests lässt sich nur noch ein Systemtest durchführen, der die Datenbank einschließt. Am Ende müssen immer zwei Entitäten getestet werden: der Code der Funktion func
und die Datenbank.
Unittests sollten
- keine externen Abhängigkeiten haben.
- schnell sein.
- keine Seiteneffekte besitzen.
Wir können das Singleton-Muster nicht für die versteckte Abhängigkeit verantwortlich machen. Das ist einfach nur schlechtes Softwaredesign. Ein besser strukturierter Code könnte so aussehen:
func(DataBaseSingleton::getInstance());
...
void func(DataBase& db) {
...
db.update("something");
...
}
Verwende die DataBase
in der Schnittstelle der Funktion. Jetzt gibt es keine versteckten Abhängigkeiten mehr. Die Funktion kann schnell und ohne Seiteneffekte ausgeführt werden. Jetzt ist sie eine Unit und damit auch Unit-testbar. Warum?
Mache aus DataBase
ein Interface und biete mindestens zwei Implementierungen an. Eine ist das ursprĂĽngliche Singleton DataBaseSingleton
und die andere ist ein Mock-Objekt: DataBaseMock
. Das DataBaseMock
imitiert das Verhalten des DataBaseSingleton
und kann als DataBase
verwendet werden. Das DataBaseMock
ist vollständig deterministisch und bringt keine Abhängigkeiten mit sich.
func(DataBaseMock::getInstance());
...
void func(DataBase& db) {
...
db.update("something");
...
}
Mein ResĂĽmee
Es erscheint müßig, für oder gegen das Singleton-Muster zu argumentieren. Jedes Muster hat seine Vor- und Nachteile, und das gilt insbesondere für das Singleton-Muster. Deshalb solltest du abwägen, ob die Vorteile im konkreten Fall die Nachteile überwiegen. Außerdem solltest du gegebenenfalls das Meyers Singleton verwenden, und das Singleton sollte Bestandteil deiner Funktionssignatur sein.
Zum Abschluss meiner Diskussion über die Vor- und Nachteile von Singletons möchte ich zwei kritische Artikel über das Singleton-Muster von Arne Mertz und Jonathan Boccara empfehlen:
- Simplify C++ by Arne Mertz: Singletons: What's the Deal?
- Fluent C++ by Jonathan Bocara: The Issues With the Singletons and Hot to Fix Them
Dein ResĂĽmee?
Bitte schreibe mir eine E-Mail an Rainer.Grimm@ModernesCpp.de, und ich werde gegebenenfalls einen weiteren Artikel zu dieser Thematik schreiben.
Wie geht's weiter?
Bis jetzt habe ich noch nicht über die Alternativen zum Singleton-Muster geschrieben. In meinem nächsten Artikel stelle ich zwei weitere Muster vor: das Monostate Muster (auch bekannt als Borg-Idiom) und Dependency Injection.
Get the invitation to the one-hour presentation of my mentoring program "Fundamentals for C++ Professionals" including Q&A
- First: 2022-10-03; 9 pm (CEST)
- Second: 2022-10-10; 9 am (CEST)
Do you want the invitation to the Zoom meeting? ⇒ Write an e-mail with the subject First or Second to info@ModernesCpp.de.
(map)