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.

In Pocket speichern vorlesen Druckansicht 3 Kommentare lesen

(Bild: Sanjin Hadzic / Shutterstock)

Lesezeit: 6 Min.
Von
  • Rainer Grimm
Inhaltsverzeichnis

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.

Die entscheidende Frage lautet: Ist 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.

Ich möchte positiv beginnen:

  • 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.

  • 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");
   ...
}

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:

Bitte schreibe mir eine E-Mail an Rainer.Grimm@ModernesCpp.de, und ich werde gegebenenfalls einen weiteren Artikel zu dieser Thematik schreiben.

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.

Modernes C++ Mentoring

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)