Das Singleton Pattern – eine Klasse für sich
Die sogenannten Design Patterns (Entwurfsmuster) helfen in der objektorientierten Programmierung den Entwicklern mit vielfach bewährten Vorlagen für die Lösung von Programmieraufgaben. Hat man aus den circa siebzig Entwurfsmustern das geeignete gefunden, wird es durch individuelle Anpassungen verfeinert. Dabei bleibt der generelle Denkansatz für das Muster aber erhalten. Das Singleton-Entwurfsmuster ist sehr leistungsfähig, hat allerdings den Ruf, ein Relikt in der objektorientierten Programmierung zu sein. Was es trotzdem kann, wo und wie es genutzt wird, erfahren Sie in unserem Ratgeber.
Was ist das Singleton Pattern?
Das Singleton Pattern gehört zur Kategorie der Erzeugungsmuster unter den Design Patterns. Eine seltener verwendete Bezeichnung ist „Einzelstück“. Seine Aufgabe besteht darin, zu verhindern, dass von einer Klasse mehr als ein Objekt erstellt werden kann. Das wird dadurch erreicht, dass das gewünschte Objekt in einer Klasse selbst erzeugt dann als statische Instanz abgerufen wird. Das Singleton zählt zu den einfachsten, aber dafür mächtigsten Patterns in der Software-Entwicklung.
Die „Gang of Four“ (GfO) – ein Team von Programmierern aus den USA – über das Singleton Pattern: „Sichere ab, dass eine Klasse genau ein Exemplar besitzt, und stelle einen globalen Zugriffspunkt darauf bereit.“
Was sind die Eigenschaften des Singleton Patterns?
Wurde mit dem Singleton-Entwurfsmuster von einer Klasse eine Instanz erstellt, sorgt es dafür, dass es auch wirklich nur bei dieser einzelnen Instanz bleibt. Das Singleton macht diese Klasse in der Software global zugänglich. Dafür gibt es in den Programmiersprachen verschiedene Methoden. Damit es bei nur einer einzigartigen Instanz bleibt, muss verhindert werden, dass Nutzer neue Instanzen erzeugen können. Das geschieht, indem der Konstruktor das Muster als „private“ deklariert. Damit kann ausschließlich der Code im Singleton das Singleton selbst instanziieren. Somit ist garantiert, dass immer nur ein und dasselbe Objekt zum Nutzer gelangen kann. Besteht diese Instanz bereits, wird auch keine neue erzeugt. Ein mögliches Singleton sieht wie folgt aus:
public class Singleton {
private static Singleton instance; // vor Zugriff von außen geschützt und statisch
private Singleton() {} // privater Konstruktor mit Zugriffsschutz von außen
public static Singleton getInstance() { // öffentliche Methode, Aufruf durch Code
if (instance == null) { // nur wenn keine Instanz besteht, dann erstelle eine neue
instance = new Singleton();
}
return instance;
}
}
Das Singleton Pattern in UML-Darstellung
In der Darstellung mit der vereinheitlichten Modellierungssprache, kurz UML (Unified Modeling Language), besteht das komplette Singleton Design Pattern nur aus einem einzigen Objekt, denn es ist ja auch nur eine singuläre Instanz einer Klasse zu erstellen.
Es ist von außen nicht möglich, an dem erzeugten Einzelstück etwas zu verändern. Genau das ist das Ziel des Einsatzes des Singleton Design Patterns.
Vor- und Nachteile des Singleton-Entwurfsmusters
Vorteile auf einen Blick
Ein Singleton lässt sich schnell und unkompliziert schreiben, da es nicht mit unzähligen (globalen) Variablen bestückt wird. Es kapselt seine Erstellung und kann damit auch genaue Kontrolle ausüben, wann und wie darauf zugegriffen wird. Ein bestehendes Singleton Pattern kann mittels Unterklassen abgeleitet werden, um neue Funktionalitäten auszufüllen. Was davon genutzt wird, wird dynamisch entschieden. Und nicht zuletzt wird ein Singleton genau dann erzeugt, wenn es benötigt wird – diese Eigenschaft trägt die Bezeichnung Lazy Loading. Der Vorgang, ein Singleton schon vorgezogen zu instanziieren – also bevor es benötigt wird – heißt Eager Loading.
Nachteile auf einen Blick
Die ungehemmte Verwendung von Singletons führt faktisch zu einem Zustand wie beim prozeduralen Programmieren (also dem nicht objektorientierten) und kann zu unsauberem Programmcode führen. Die globale Verfügbarkeit von Singleton-Entwurfsmustern birgt Gefahren, wenn sensible Daten damit behandelt werden. Werden Änderungen am Singleton vorgenommen, ist nicht nachvollziehbar, welche Programmteile davon betroffen sind. Das erschwert die Wartung der Software, weil Fehlfunktionen nur schwer zurückverfolgt werden können. Die globale Verfügbarkeit macht auch das Löschen von Singletons schwierig, da immer Bestandteile einer Software auf dieses Singleton verweisen können. Bei Anwendungen mit vielen Benutzern (Mehrbenutzeranwendungen) kann ein Singleton die Performance senken, denn es stellt – da eben singulär – eine Datenengstelle dar.
Das Singleton-Entwurfsmuster im „echten Leben“
Das Singleton kommt meist dann zum Einsatz, wenn immer wiederkehrende Aufgaben in einer Programmroutine zu erledigen sind. Dazu gehören Daten, die in eine Datei zu schreiben sind, z. B. beim Logging, oder es sind Druckaufträge, die immer wieder in einen einzelnen Drucker-Puffer geschrieben werden sollen. Weil auch Treiber und Cachemechanismen stets gleiche Abläufe aufweisen, wird auch dort gern das Singleton Design Pattern verwendet.
Da es sehr schwierig ist, das Singleton Pattern zu testen, verdeutlichen wir hier seine Arbeitsweise am Beispiel einer kleinen Firmenrepräsentanz, in der mehrere Mitarbeiter einen Drucker benutzen. Ein Beispiel, das der Praxis nahekommt, stellt die Design Pattern Tutorial Series von Daniel H. Jacobsen vor. Daran orientiert sich das nachfolgende Singleton-Entwurfsmuster.
Schickt ein Nutzer eine Anfrage an den Drucker, wird vom Singleton die „Frage“ gestellt: „Gibt es schon ein Printer-Objekt? Wenn nicht, dann erstelle eins.“ Gelöst wird das mit einer Anweisung im if/then-Charakter (return Drucker == Null ?). Um den Zugriff und damit Veränderungen zu verhindern, werden einzelne Variablen und der Drucker auf „private“, also nicht „public“, gesetzt.
public class Drucker {
private static Drucker drucker;
private int AnzahlSeiten;
private Drucker() {
}
public static Drucker getInstance() {
return drucker == Null ?
drucker = new Drucker() :
drucker;
}
public void print(String text){
System.out.println(text +
"\n" + "Anzahl der heute gedruckten Seiten" + ++ AnzahlSeiten +
"\n" + "---------");
}
}
Als nächstes werden die Mitarbeiter der Niederlassung „gekapselt“. Die Strings für die Namen, die Position und die Tätigkeit im Unternehmen sind ebenfalls auf „private“ gesetzt.
public class Employee {
private final String name;
private final String position;
private final String taetigkeit;
public Mitarbeiter(String name, String position, String taetigkeit) {
this.name = name;
this.position = position;
this.taetigkeit = taetigkeit;
}
public void printCurrent taetigkeit (){
Drucker drucker = Drucker.getInstance();
drucker.print("Mitarbeiter: " + name + "\n" +
"Position: " + position + "\n" +
"Taetigkeit: " + taetigkeit + "\n");
}
}
Schließlich werden die beiden Singletons in eine Ausgaberoutine eingebunden.
public class Main {
public static void main(String[] args) {
Mitarbeiter andreas = new Mitarbeiter ("Andreas",
"Chef",
"Leitet die Niederlassung");
Mitarbeiter julia = new Mitarbeiter ("Julia",
"Beraterin",
"Berät Kunden bei Reklamationen");
Mitarbeiter tom = new Mitarbeiter ("Tom",
"Verkäufer",
"Verkauft die Produkte");
Mitarbeiter stefanie = new Mitarbeiter ("Stefanie",
"Entwicklerin",
"Wartung der IT in der Niederlassung.");
Mitarbeiter matthias = new Mitarbeiter ("Matthias",
"Buchhalter",
"Finanzbuchhaltung der Niederlassung.");
andreas.printCurrentTaetigkeit();
julia.printCurrentTaetigkeit ();
tom.printCurrentTaetigkeit ();
stefanie.printCurrentTaetigkeit ();
matthias.printCurrentTaetigkeit ();
}
}