
Das IT-Magazin der ORDIX AG mit Fachbeiträgen zu Datenbanken, Unix und Java/XML.
Nach der Theorie und einem erklärenden Beispiel im ersten Teil über Entwurfsmuster zeigen die folgenden Codefragmente, Strukturdiagramme und Beschreibungen, wie und wann sich Entwurfsmuster in der Praxis einsetzen lassen.
Das Singleton-Entwurfsmuster ist ein objektbasiertes Erzeugungsmuster und gewährleistet, dass von einer Klasse genau eine Instanz existiert und stellt eine globale Zugriffsmethode dafür bereit. Beispiele für solche Klassen gibt es viele, oft sind es Dienstleistungsklassen wie z. B. ein Logger, Fenstermanager, Druckerspooler oder etwa ein Datenbankmanager.
Um sicherzustellen, dass es nur ein Exemplar einer Klasse gibt, überlassen wir die Objekterzeugung und -verwaltung der Klasse selbst. Über eine globale Zugriffsmethode können Klienten auf dieses Exemplar zugreifen (Abb. 1).
![]() |
| Abb. 1: Struktur des Singleton-Musters. |
Die öffentliche, statische Schnittelle getInstance() liefert die einzige Instanz der Klasse: uniqueInstance. Ist die Instanz noch nicht vorhanden, wird sie erzeugt. Um sicherzustellen, dass nicht von außerhalb ein Objekt dieser Singleton-Klasse erzeugt werden kann, wird der Konstruktor einfach als private deklariert und kann somit nur innerhalb der eigenen Klasse benutzt werden.
Das Codebeispiel zeigt, wie einfach die Umsetzung des Singleton-Musters ist. Die Vorteile des Musters sind offensichtlich: Die Singleton-Klasse kapselt sein einziges Exemplar und verfügt somit über eine strikte Kontrolle darüber, wie und wann darauf zugegriffen werden kann. Sollte sich im Laufe des Entwicklungsprozesses herausstellen, dass doch mehr als ein Exemplar benötigt wird, hilft das Muster dabei, die Anzahl genau festzulegen und zu kontrollieren (siehe Abb. 2).
public class Singleton {
// privater Konstruktor
private Singleton() { ; }
// einzige Instanz dieser Klasse
private static Singleton uniqueInstance = null ;
// Zugriffsmethode
public static Singleton getInstance() {
if (uniqueInstance == null){
uniqueInstance = new Singleton() ;
}
return uniqueInstance ;
}
} |
| Abb. 2: Implementierung einer Singleton-Klasse in Java. |
Dieses Verhaltensmuster ermöglicht es, dass die Änderung des Zustands eines Objekts dazu führt, andere Objekte zu benachrichtigen und automatisch zu aktualisieren.
Die zentralen Objekte in diesem Muster sind das Subjekt und die Beobachter. Ein Subjekt (Observable) kann mehrere abhängige Beobachter (Observer) besitzen, die sich für den Zustand des Subjekts interessieren. Ändern sich die Daten des Subjekts, werden die angemeldeten Beobachter darüber informiert.
Ein gutes Beispiel für die Verwendung dieses Musters ist ein Datenobjekt mit mehreren grafischen Repräsentationen, z. B. eine Tabelle, ein Säulendiagramm und ein Kreisdiagramm. Diese Darstellungsobjekte sind die Beobachter und aktualisieren ihre Anzeige, sobald sie über die Änderung der Daten vom Datenobjekt informiert werden. Somit ist gewährleistet, dass die verschiedenen Anzeigen synchron und konsistent den aktuellen Zustand ihres Datenobjekts darstellen.
Das Subjekt kennt seine Beobachter und bietet Schnittstellen zum Hinzufügen und Entfernen von Beobachtern. Die Aktualisierungsschnittstelle des Beobachters bildet die Methode update(). Ein konkretes Subjekt speichert den für einen konkreten Beobachter relevanten Zustand und benachrichtigt über notify() die angemeldeten Beobachter, wenn sich sein Zustand ändert. Dazu ruft das konkrete Subjekt auf allen registrierten Beobachtern deren update()-Methode auf. In dieser Aktualisierungsmethode holt sich der konkrete Beobachter den Zustand des konkreten Subjekts.
Somit sind die Daten des konkreten Subjekts (subjectState) und die der angemeldeten konkreten Beobachter (observerState) synchronisiert (siehe Abb. 3).
![]() |
| Abb. 3: Struktur des Beobachtermusters. |
Wegen der häufigen Verwendung, insbesondere beim Programmieren von grafischen Benutzungsschnittstellen, unterstützt die Java Standard API den Entwickler bei der Umsetzung des Beobachtermusters. Das Subjekt ist ein Exemplar der Bibliotheksklasse java.util.Observable. Ein Beobachter implementiert das Interface Observer.
Im folgenden Beispielprogramm sehen wir, wie Observable- und Observer-Objekte miteinander kommunizieren.
Das Observable-Objekt ist eine Instanz der Klasse Mailverteiler, an dessen Zustandsänderung (das ist der Mailempfang) die eingetragenen Mailempfänger als Observer-Objekte interessiert sind. Es gibt verschiedene Mailverteiler für bestimmte Themen. Einen Mailverteiler interessiert überhaupt nicht, wer genau seine Nachrichten empfangen möchte. Er ist nur dafür zuständig, E-Mails zu empfangen und diese an die registrierten Mailempfänger weiterzuleiten.
Sobald der Mailverteiler eine neue E-Mail empfängt, macht er mit setChanged() auf die Änderung seines Zustands aufmerksam. Dann wird mit notifyObservers(E-Mail) eine Benachrichtigung an die registrierten Observer geschickt und die E-Mail direkt weitergeleitet.
Letzteres entspricht nicht ganz dem Strukturmuster in Abb. 3, nach dem sich die Observer selbst den Zustand vom Subjekt holen. Der direkte Datentransfer über notifyObservers(Object) ist jedoch eine gängige und praktische Variante des Datenaustauschs.
setChanged() setzt intern ein Flag, welches von notifyObservers(Object) abgefragt wird. Nach dem Aufruf von notifyObservers(Object) wird dieses Flag wieder gelöscht.
Dies kann ebenso manuell mit clearChanged() geschehen. notifyObservers (Object) sendet nur dann eine Benachrichtigung an die Observer, wenn auch das Flag gesetzt ist (vgl. Abb. 4).
import java.util.Observable;
public class Mailverteiler extends Observable
{ private String name = null;
public Mailverteiler(String name) {this.name = name;}
public void empfange(String eMail) {
//Zustandsänderung
setChanged();
//Angemeldete Observer benachrichtigen und
//empfangene E-Mail weiterleiten
notifyObservers(eMail);
}
public String toString() {return name;}
} |
| Abb. 4: Die Klasse Mailverteiler verschickt neu eintreffende E-Mails an alle angemeldeten Mailempfänger. |
Die Mailempfänger müssen das Interface Observer implementieren. Damit versichern sie, die Methode update(Observable, Object) zu implementieren, die bei Änderungen vom Observable-Objekt automatisch aufgerufen wird. Das Interface Observer besteht nur aus dieser einen Methode. Der zweite Parameter ist eine Object-Instanz, in unserem Beispiel (Abb. 5) die E-Mail, die mit Hilfe des Befehls notifyObservers(E-Mail) verschickt wurde.
import java.util.* ;
class Mailempfaenger implements Observer {
private String eMailAdress = null;
public Mailempfaenger(String eMailAdress) {
this.eMailAdress = eMailAdress;
}
// Implementierung der update()-Methode
// des Interface Observer.
public void update(Observable o, Object obj) {
System.out.println(eMailAdress+“ empfängt
die E-Mail \““+obj+“\“ von \““+o+“\““);
}
public static void main(String args[]) {
//Erzeugen von Mailverteilern(Observable)
//und Mailempfaengern(Observer) ;
Mailverteiler java =
new Mailverteiler(„java_news@firma.de“);
Mailverteiler oracle =
new Mailverteiler(„oracle_news@firma.de“);
Mailempfaenger hansi =
new Mailempfaenger(„hansi@firma.de“);
Mailempfaenger klaus =
new Mailempfaenger(„klaus@firma.de“);
Mailempfaenger karin =
new Mailempfaenger(„karin@firma.de“);
//Anmelden der Observer beim Observable
java.addObserver(hansi);
java.addObserver(karin);
oracle.addObserver(klaus);
//Zustandsänderung der Observable
java.empfange(„Sun veröffentlicht Release
Candidate des JDK 1.4“);
oracle.empfange(„Oracle 9i integriert
JDeveloper 5.0“);
//Abmelden eines Observer vom Observable
java.deleteObserver(hansi);
java.empfange(„Sun veröffentlicht Java Web
Services Developer Pack“);
}
}
|
| Abb. 5: Die Klasse Mailempfaenger implementiert das Observer-Interface durch update(Observable,Object). |
karin@firma.de empfängt die E-Mail “Sun veröffentlicht Release Candidate des JDK 1.4“ von “java_news@firma.de“ hansi@firma.de empfängt die E-Mail “Sun veröffentlicht Release Candidate des JDK 1.4“ von “java_news@firma.de“ klaus@firma.de empfängt die E-Mail “Oracle 9i integriert JDeveloper 5.0“ von “oracle_news@firma.de“ karin@firma.de empfängt die E-Mail “Sun veröffentlicht Java Web Services Developer Pack“ von “java_news@firma.de“ |
| Abb. 6: Programmausgabe nach der Ausführung der Mailempfaenger.class. |
Natürlich braucht es einige Zeit, bis man Muster kennt und sie einsetzen kann, aber das einmal erworbene Wissen lässt sich während der Entwicklung oftmals einsetzen, da die Muster ja immer wiederkehrende Probleme und deren einfache Lösung beschreiben. Man muss nur wissen, welche Muster welche Probleme lösen und kann dann recht schnell die Lösung in Form von Programmcode erfolgreich umsetzen.
Ingo Vogt (info@ordix.de).