Home ORDIX AG             Dienstleistung             Trainingsshop    Kunden / Referenzen Aktuelles    Kontakt
Home  Pfeil  ORDIX News  Pfeil  1/2002
suche: 

ORDIX News Archiv

Das IT-Magazin der ORDIX AG mit Fachbeiträgen zu Datenbanken, Unix und Java/XML.

Entwurfsmuster Teil II:

Implementierung von Entwurfsmustern

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.

Single-Dasein

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

Struktur des Singleton-Musters
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.

Das Beobachter-Pattern (Observer)

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.

Struktur des Beobachtermusters

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

Implementierung einer Singleton-Klasse in Java
Abb. 3: Struktur des Beobachtermusters.

Das Beobachtermuster in Java

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.

Die Klasse Observable

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 Schnittstelle Observer

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.

Fazit

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