Grundlagen der Programmierung: Entwurfsmuster

Es kann nur einen geben: Singleton

Testen Sie unsere 2005 Kurse

10 Tage kostenlos!

Jetzt testen Alle Abonnements anzeigen
Soll von einer Klasse nur genau ein Exemplar erzeugt werden können und dürfen, ist "Singleton" das Entwurfsmuster der Wahl.
09:57

Transkript

Das Entwurfsmuster, das ich Ihnen in diesem Video vorstellen möchte, ist das Singleton. Der Name lässt schon ein bisschen darauf schließen, worum es hier geht. Nämlich von einer Klasse soll nur genau eine Instanz existieren. Außerdem soll natürlich allen interessierten Clients ein Zugang zu dieser Instanz ermöglicht werden und dieser Zugriff sollte für die Clients möglichst einfach sein. Aber wozu braucht man so was? Sie brauchen das immer dann, wenn Sie einen zentralen Zugangspunkt für irgendetwas benötigen. Beispielsweise könnte das sein ein zentraler Cache, um Daten vorzuhalten, die Sie ständig in Ihrer Anwendung benötigen. Wenn jedes Objekt in Anwendung seinen eigenen Cache bekommen würde, wäre der Nutzen vermutlich nicht allzu groß. Auch für die zentrale Verwaltung externer Ressourcen kann eine einzige Instanz einer Klasse interessant sein, zum Beispiel für den Zugriff auf Log-Dateien oder programmweite Konfigurationsdaten und ein drittes Beispiel wäre das effiziente zur Verfügung stellen von Daten, an denen Sie keine Änderungen vornehmen möchten, die Sie nur lesen wollen. Wenn solche Daten zum Beispiel in einer externen Datenbank liegen, dann kann es sinnvoll sein beim Start der Anwendung, diese Daten einmal einzulesen und dann zentral sozusagen im Memory der Anwendung zur Verfügung zu stellen. Was ist jetzt eigentlich aber genau das Problem an diesem Problem? Wir haben hier eine Beispielklasse. Das ist die Klasse, von der es nur eine Instanz geben soll und die bietet irgendwelche Funktionalitäten an. Wenn nun ein Client kommt und möchte auf eine Instanz dieser Klasse zugreifen, dann macht er es typischerweise, indem er den Operator new aufruft gefolgt vom Constructor. Was er dann zurückbekommt, ist genau das, was er haben will, nämlich eine Instanz dieser Klasse. Soweit so gut. Nun kommt ein anderer Client des Weges. Auch er möchte gerne Zugriff auf eine Instanz haben. Er ruft also ebenfalls den Operator auf mit dem Constructor und was bekommt er. Er bekommt eine zweite Instanz. Das ist eben genau nicht das, was wir wollen. Bevor wir uns der eigentlichen Lösung zuwenden, möchte ich einen kurzen Exkurs einfügen und zwar zu Java enum. Das ist ein Sprachelement, das mit Java 5 eingeführt wurde, also im Jahre 2004. Das gibt es also schon eine ganze Weile. Enums ermöglichen es sozusagen eigene Datentypen zu definieren. Wir haben hier zum Beispiel den Datentyp Wochentag und die möglichen Werte eines solchen Wochentags sind Montag, Dienstag, Mittwoch, Donnerstag, Freitag, Samstag, Sonntag. Dann kann man diesen Datentyp verwenden, um Variablen anzulegen und die natürlich auch mit Werten zu belegen. Für die Deklaration der Variablen verwende ich den Enum-Typ und für den Wert nehme ich den Typ einen Punkt gefolgt von dem Namen des Wertes sozusagen. Der Vorteil im Vergleich zu zum Beispiel Strings ist, dass der Compiler hier eine Typprüfung vornehmen kann. Das heißt, wenn ich hier versuche einen anderen Wert anzugeben, also zum Beispiel April, dann bekomme ich einen Compilerfehler. Bei einem String würde ich wahrscheinlich erst zur Laufzeit merken, dass irgendwas nicht stimmt. Ist also eine bequeme Sache solche Enums. Was hat das nun aber mit unserem Einzige-Instanz-Problem zu tun? Dazu werfen wir einen Blick hinter die Kulissen. Wenn ich eine solche Deklaration in meinen Quelltext reinschreibe, dann generiert mir der Java Compiler daraus eine vollständige Klassendeklaration, die ungefähr so aussieht. Es wird hier eigentlich eine ganz normale Klasse deklariert mit dem Namen unseres Enums. Diese Klasse ist Unterklasse von Enum, eine Klasse aus der Standardbibliothek. In dieser Klasse haben wir dann Attribute und zwar für jeden Wert, den wir hier oben angegeben haben genau eines und diese Attribute sind alle public static und final. Außerdem werden diese Attribute alle sofort initialisiert und zwar jeweils mit einer Instanz dieser Klasse Wochentag. Alle Attribute sind static. Das heißt, wenn die Klasse geladen wird, dann werden alle diese Attribute sofort initialisiert. Durch das public sind sie von außen sichtbar. Das heißt, wir können dann über den Namen der Klasse auf diese Attribute zugreifen. In einer normalen Java-Anwendung wird jede Klasse eigentlich nur einmal geladen. Das heißt, wir haben in unserer gesamten Anwendung nur einen Montag, einen Dienstag, einen Mittwoch usw. Außerdem, wie wir hier sehen können, ist der generierte Constructor private. Das heißt, er kann von außen überhaupt nicht aufgerufen werden. Der Zugriff auf die einzelnen Instanzen Montag, Dienstag, Mittwoch usw. erfolgt, wie wir gerade gesehen haben, einfach über den Namen der Klasse, einen Punkt und dann eben den Namen des einzelnen Attributs. Nun werden Sie vielleicht denken, das ist alles gut und schön, aber wir wollen nicht 7 Instanzen sondern eigentlich nur eine. Kein Problem. Dann holen wir uns ein Enum mit nur einem Wert. Das ist das Enum und den einen Wert habe ich der Einfachheit halber INSTANZ genannt. Werfen wir einen Blick auf die Klasse, die daraus generiert wird. Es ist wieder eine Unterklasse von Enum. Hier haben wir ein Attribut. Das Attribut heißt INSTANZ, so wie es da oben steht. Hier wird das Objekt erzeugt. Das Attribut ist public. Es ist static und der Constructor ist natürlich private. Das heißt, keiner kann irgendwelche Objekte erzeugen von außen und das eine Objekt ist schon da. Wird die Klasse geladen, dann wird das Objekt erzeugt und ab da können keine weiteren Objekte mehr erzeugt werden. Die Clients können also lediglich auf das eine vorhandene Objekt zugreifen. Das sieht dann so aus. Wir nehmen den Namen des Enums, den Namen dieses Attributs und bekommen auf diese Weise den Zugriff auf das eine Objekt, das da ist. Eigentlich recht einfach für den Client. Ein letztes Detail fehlt uns noch, um eine funktionierende Lösung zu haben und das sind die Methoden. Unser Singleton soll auch irgendwas tun können. Wie Sie hier sehen können, ist das Enum eine ganz normale Klasse. Das heißt, ich kann, wie ich das bei jeder Klasse machen kann, hier auch Methoden hinzufügen. Die Methoden schreibe ich einfach in meine Enum-Deklaration hinten dran. Das sieht dann zum Beispiel so aus. Enum Zentrale mit dem einem Wert INSTANZ und danach folgen dann die komplexen Geschäftsmethoden, die das Singleton anbieten soll. Möchte nun ein Client Zugriff auf unser Singleton haben, dann kann er das nicht mehr mit new machen, denn, wie Sie gerade gesehen haben, der Constructor ist private. Das heißt, ein Aufruf von außen ist nicht möglich. Stattdessen greift der Client auf die eine Instanz zu, die wir haben eben über den Namen des Enums, Punkt und den Namen des Wertes, also Zentrale.INSTANZ und dann bekommt er genau das Objekt zurück, was beim Laden des Enums bereits erzeugt wurde. Prinzipiell waren wir ganz zu Anfang auch schon soweit. Nun kommt aber der zweite Client des Weges. Auch er sagt Zentrale.INSTANZ, bekommt jetzt dieselbe Instanz zurück wie der erste Client auch. Das heißt, Aufgabe erfüllt. Problem gelöst. Es wird nämlich tatsächlich nur genau eine Instanz dieser Klasse erzeugt. Es gibt einen zentralen Zugangspunkt für diese Instanz auf diesen alle Clients zugreifen können und es gibt außerdem keine zusätzlichen Verpflichtungen für den Client. Er muss sich also nicht selbst darum kümmern, dass es auch immer dieselbe Instanz ist und dass er nicht aus Versehen andere erzeugt. Beim Einsatz des Singletons ist es allerdings wichtig, dass Sie sich einer Sache bewusst sind. Es gibt die Gefahr, dass das Singleton zum Flaschenhals wird. Solange der Singleton nur Leseoperationen zur Verfügung stellt, ist alles in Ordnung. Problematisch wird es aber, wenn Clients auch Schreiboperationen durchführen können und das Ganze in einer Anwendung, in der mehrere Threads gleichzeitig auf das Singleton zugreifen können. Dann muss sich das Singleton darum kümmern, dass sich die einzelnen Schreibzugriffe nicht in die Quere kommen und es kann zu Wartezeiten für die Clients kommen. Je mehr Clients zugreifen möchten und je länger die Schreiboperationen dauern, desto länger werden die Warteschlangen. Damit können all die Vorteile, die man sich durch das parallele Ausführen von Prozessen eigentlich erwartet, komplett dahin sein. Ein weiterer Punkt, den man zumindest im Hinterkopf behalten sollte, ist der Umstand, dass unsere enum-Lösung eigentlich nur ein Singleton pro JVM garantiert. Das heißt, wenn Sie mehrere Anwendungen gleichzeitig ausführen, die alle dasselbe Enum benutzen, dann gibt es natürlich pro Anwendung eine Instanz und nicht insgesamt eine. Wenn man ganz genau sein will, ist es eigentlich nicht mal ein Singleton pro JVM sondern eines pro ClassLoader. Der ClassLoader ist für das Laden der Klassen zuständig und in einer normalen Java-Anwendung gibt es auch in der Regel nur einen davon. In verteilten Anwendungen oder wenn Sie irgendwelche Frameworks nutzen oder vielleicht auch Java EE Application Server, dann ist es eigentlich sogar die Regel, dass es mehrere ClassLoader gibt. Damit ist es natürlich dann nicht mehr weit her mit Singleton. Für solche Anwendungsfällte stellen aber diverse Frameworks oder auch die Java EE Spezifikation eigene Lösungen bereit, auf die ich jetzt hier nicht näher eingehen möchte. Sollten Sie in Betracht ziehen, komplett Ihre eigene Lösung zu stricken, indem Sie zum Beispiel das als Basis nehmen, was der Compiler aus einem Enum generiert, dann müssen Sie ein paar zusätzliche Dinge beachten. Auf jeden Fall müssen Sie natürlich zuerst bei konkurrierenden Zugriffen auf die Threadsicherheit achten. Ein echtes Problem könnten Serialisierung / Deserialisierung sein. Serialisierung in Java heißt, dass ein Java-Objekt in einem Bytestrom umgewandelt wird. Dieser Bytestrom kann dann zum Beispiel in einer Datei gespeichert werden oder übers Netzwerk an einen anderen Rechner übertragen werden, der dann aus diesem Bytestrom wieder ein Objekt herstellt. An dieser Beschreibung merken Sie schon, dass das die Hintertür ist, über die ich aus einem Singleton Objekt problemlos ein zweites Objekt machen kann. Bei Enums haben wir die spezielle Enum-Magie. Da kümmert sich also die Java-Laufzeitumgebung um das Problem. Bei eigenen Implementierungen müssen wir uns auch selbst darum kümmern. Dazu müssen wir eine Methode implementieren, die heißt readResolve, die Sie im Singleton hinzufügen müssen. Wenn Sie den Namen der Methode einmal kennen, dann finden Sie auch in der Javadoc die entsprechenden Informationen dazu. Schließlich gibt es noch einen weiteren interessanten Aspekt, den Sie berücksichtigen müssen. Das sind sogenannter Out-of-order-writes. Was ist das jetzt nun schon wieder? Wenn ein Java-Objekt erzeugt wird, dann wird der Constructor abgearbeitet, der alle Attribute der Reihe nach initialisiert. Ist der Constructor erfolgreich durchgelaufen, bekommt der Client eine Referenz auf das initialisierte Objekt. Nun ist es aber prinzipiell möglich, dass die Änderungen der Initialisierung, die der Constructor vorgenommen hat, für den Client noch nicht sichtbar sind, obwohl der die Referenz schon bekommen hat. Das heißt, der Constructor hat zwar aus seiner Sicht alles schon initialisiert, für den Client sind diese Änderungen aber noch nicht zu sehen. Um sicherzustellen, dass der Client tatsächlich die initialisierten Variablen zugreift und nicht irgendwo ins Leere hinein, müssen wir unsere Instanzvariable noch mit dem Schlüsselwort volatile versehen. Sie sehen also, eine Zu-Fuß-Implementierung des Singleton Patterns in Java ist nicht ganz trivial und für einen Großteil der Anwendungsfälle funktioniert die enum-Lösung sehr gut. Damit kennen Sie nun das Entwurfsmuster Singleton und wissen auch, wie Sie es in Java mit Enums sehr einfach umsetzen können.

Grundlagen der Programmierung: Entwurfsmuster

Erhalten Sie einen fundierten Einstieg in das Thema Entwurfsmuster (Design Patterns) und erfahren Sie, wie Sie Entwurfsmuster in objektorientierten Programmiersprechen umsetzen.

2 Std. 49 min (33 Videos)
Derzeit sind keine Feedbacks vorhanden...
 

Dieser Online-Kurs ist als Download und als Streaming-Video verfügbar. Die gute Nachricht: Sie müssen sich nicht entscheiden - sobald Sie das Training erwerben, erhalten Sie Zugang zu beiden Optionen!

Der Download ermöglicht Ihnen die Offline-Nutzung des Trainings und bietet die Vorteile einer benutzerfreundlichen Abspielumgebung. Wenn Sie an verschiedenen Computern arbeiten, oder nicht den ganzen Kurs auf einmal herunterladen möchten, loggen Sie sich auf dieser Seite ein, um alle Videos des Trainings als Streaming-Video anzusehen.

Wir hoffen, dass Sie viel Freude und Erfolg mit diesem Video-Training haben werden. Falls Sie irgendwelche Fragen haben, zögern Sie nicht uns zu kontaktieren!