Java - von am Donnerstag, September 3, 2009 2:52 - 0 Kommentare

Decorator Pattern in Java

Mit dem Decorator Pattern, in der deutschen Übersetzung naheliegenderweise Dekorierer genannt, lässt sich ein Objekt dynamisch um Fähigkeiten, von der Gang of Four Zuständigkeiten genannt, erweitern. Anstatt Unterklassen zu bilden und eine Klasse damit um Fähigkeiten bzw. Verhalten zu erweitern, lässt sich mit dem Einsatz des Decorator Patterns die Erzeugung von Unterklassen vermeiden. Das im Folgenden aufgeführte Java-Beispiel beschreibt das Decorator Pattern, wie es von der Gang of Four beschrieben wurde, anhand eines einfachen Codebeispiels in Java.

Zum leichteren Verständnis der Design Patterns möchte ich Ihnen den Bestseller Entwurfsmuster von Kopf bis Fuß nahelegen, welches die meiner Meinung nach verständlichste und beste Einführung in die Welt der Design Patterns bietet. Die in dem Buch dargestellten Design Patterns decken alle in der Softwareentwicklung relevanten Anwendungsbereiche ab, ganz gleich ob Sie eine reguläre Java-Anwendung erstellen oder komplexe fachliche Anforderungen umsetzen.

Kategorie

Das Decorator Pattern wird zu den „Structural Patterns“ gezählt, da es ermöglicht, dass Klassen und Objekte größere Strukturen bilden. Hierbei muss beachtet werden, dass das Decorator Pattern zur Untergruppe der Objektmuster gehört, da die dadurch beschriebenen Strukturen zur Laufzeit änderbar sind. In einem Artikel zum Adapter Pattern gehen wir auf die Untergruppe der Klassenmuster ein, deren Strukturen zur Laufzeit festgelegt sind.

Einsatzbereich

Das Decorator Pattern kann eingesetzt werden, wenn:

  • … einzelnen Objekten zusätzliches Verhalten und zusätzliche Eigenschaften hinzugefügt werden sollen, ohne andere Objekte zu beeinflussen.
  • … das Verhalten und die Eigenschaften auch wieder entfernt werden sollen.
  • … die Bildung von Unterklassen zu einem zu komplexen und großen Klassenmodell führen würde.
  • … die Bildung von Unterklassen nicht möglich ist, da z.B. Klassen nicht sinnvoll abgeleitet werden können.
  • … die Implementation des zusätzlichen Verhaltens und der zusätzlichen Eigenschaften in der Ursprungsklasse zu komplex und zu oft zu überflüssigem Code führen würde.

UML-Klassendiagramm: Decorator Pattern

Das UML-Klassendiagramm für das Decorator Pattern sieht folgendermaßen aus:

UML-Modell: Decorator Pattern

In dem Java-Beispielcode werden wir auf die einzelnen Bestandteile des Diagramms eingehen und erläutern, welche Klasse und welches Objekt für welche Aufgaben zuständig ist.

Beispiel

Bevor wir auf ein Beispiel eingehen, möchte ich darauf hinweisen, dass praktisch jeder Java-Entwickler mit dem Decorator-Pattern in Berührung gekommen ist, ohne sich dessen unbedingt bewusst gewesen zu sein. Die Entwicklung von GUI-Oberflächen wäre ohne das Decorator-Pattern und seine verwandten Design Patterns schwer zu realisieren. Ein weiteres Beispiel sind die Input und Output-Streams der Java-Klassenbibliotheken. Die Klasse ZIPOutputStream beispielsweise erweitert nur das Verhalten der Klasse OutputStream um das Komprimieren von Daten und reicht diese anschließend weiter an die nächste Operation in der Klasse OutputStream.

Dieses Beispiel möchten wir aufgreifen, indem wir das Decorator Pattern anhand rudimentärer Komprimierungsklassen veranschaulichen.

Wir beginnen mit der wichtigsten Klasse, der Komponente, die wir um zusätzliches Verhalten erweitern möchten. Zu Anfang kann sie, bzw. ihre konkrete Implementierung, nichts weiter, als aus einer Datenbank einen bestimmten Inhalt auszulesen und diesen in einer Datei zu speichern. Anschließend wird der Dateiname dieser Datei zurückgegeben. Der Einfachheit halber ist das Auslesen der Datenbank und das Speichern im Beispiel nicht implementiert, der Code ist hier also bewusst etwas unklar gehalten.

package de.theserverside.designpatterns.decorator;

/**
* Stellt im UML-Klassendiagramm "Component" dar, von der eine
* konkrete Klasse abgeleitet wird, die ihr Standardverhalten
* implementiert.
*/
public abstract class Textdatei {
public abstract String speichern(String dateiname);
}

Wie man sieht ist das nur die nackte abstrakte Klasse. Ihr konkretes Verhalten, also das Speichern der übergebenen Daten einer Datei und das Zurückgeben des Dateinamens, muss in einer abgeleiteten Klasse implementiert werden:

package de.theserverside.designpatterns.decorator;

/**
* Stellt im UML-Klassendiagramm "ConcreteComponent" dar. Sie
* implementiert auch das Standardverhalten der Komponente, in
* diesem Fall das Speichern der Datei. Sie wird später bei der
* Implementierung zusätzlichen Verhaltens in die entsprechenden
* Methoden "reingereicht".
*/
public class KonkreteTextdatei extends Textdatei {
/**
* Diese Methode speichert den Inhalt einer bestimmten
* Datenbankspalte in einer Datei und liefert den Namen der
* Datei zurück.
*
* @return Name der gespeicherten Datei
*/
@Override
public String speichern(String dateiname) {
/*
* Hier steht der Code zum Abfragen der Datenbank und
* Speichern des Inhalts im Dateisystem.
*/
System.out.println("Daten gespeichert!");
return dateiname;
}
}

Wir haben jetzt eine Klasse erstellt, die ein Verhalten definiert (Textdatei.speichern(...)), und eine Klasse, die dieses Verhalten implementiert (KonkreteTextdatei.speichern(...)).
Was nun fehlt, ist der Decorator und seine konkreten Implementierungen.

Wir beginnen mit der Definition des Decorators. Noch einmal zur Erklärung: der Decorator definiert eine abstrakte Oberklasse, von der konkrete Implementierungen des zusätzlichen Verhaltens der Klasse Textdatei (genaugenommen seiner Implementierung KonkreteTextdatei) abgeleitet werden.

package de.theserverside.designpatterns.decorator;

/**
* Stellt im UML-Klassendiagramm "Decorator" dar, die
* Basisklasse aller Erweiterungen des Verhaltens der Klasse
* Textdatei ("Component").
*/
public abstract class TextdateiDekorierer extends Textdatei {
/**
* Stellt die Basisimplementation für das erweiterte
* Verhalten bereit.
*/
@Override
public abstract String speichern(String dateiname);
}

Die Vorbereitungen sind nun getroffen, nun folgt die Implementierung des erweiterten Verhaltens. Dort sehen wir, dass die Aufrufe geschachtelt werden, indem dem Konstruktor eine Referenz auf den nächsthöheren Decorator mitgegeben wird, der daraufhin vor dem Aufruf des erweiterten Verhaltens zuerst aufgerufen wird.

Im ersten Beispiel erweitern wir das Verhalten um eine Umwandlungsfunktion für HTML-Entities. Das heisst, dass der Inhalt der Datei nach Sonderzeichen wie Ä, Ö, Ü etc. durchsucht wird und diese in ihre HTML-Pendants umgewandelt werden, also in Ä, Ö und Ü.

package de.theserverside.designpatterns.decorator;

/**
* Stellt im UML-Klassendiagramm "ConcreteDecorator" dar, die
* das Verhaltens der Klasse Textdatei ("Component") erweitert.
*/
public class EntitiesUmgewandeltTextdateiDekorierer extends
TextdateiDekorierer {
private final Textdatei textdatei;

/**
* Instanziiert die Klasse und sichert die Instanz auf den
* nächsthöheren Decorator in der Hierarchie.
*/
public EntitiesUmgewandeltTextdateiDekorierer(
Textdatei textdatei) {
this.textdatei = textdatei;
}

/**
* Erweitert das Verhalten der Implementation der Klasse
* Textdatei, bzw. seiner Implementation in der Klasse
* KonkreteTextdatei, um das Umwandeln der Sonderzeichen.
*/
@Override
public String speichern(String dateiname) {
/*
* Reicht die Operation an den nächsthöheren Decorator
* in der Hierarchie weiter und wendet anschließend das
* erweiterte Verhalten (umwandeln) an.
*/
return umwandeln(textdatei.speichern(dateiname));
}

/**
* Beispielhafte Erweiterung des Verhaltens der Klasse
* Textdatei.
*/
private String umwandeln(String dateiname) {
/*
* Hier findet das erweiterte Verhalten statt, in diesem
* Fall z.B. das Umwandeln von Sonderzeichen in
* HTML-Entities. Ein "Ä" wird zum "Ä" etc..
*/
System.out
.println("HTML-Entities in der Datei umgewandelt!");
return dateiname;
}
}

Die zweite Erweiterung des Verhaltens erhält einen Dateinamen als Eingabe und komprimiert diese Datei:

package de.theserverside.designpatterns.decorator;

/**
* Stellt im UML-Klassendiagramm "ConcreteDecorator" dar, die
* das Verhaltens der Klasse Textdatei ("Component") erweitert.
*/
public class KomprimierteTextdateiDekorierer extends
TextdateiDekorierer {
private final Textdatei textdatei;

/**
* Instanziiert die Klasse und sichert die Instanz auf den
* nächsthöheren Decorator in der Hierarchie.
*/
public KomprimierteTextdateiDekorierer(Textdatei textdatei) {
this.textdatei = textdatei;
}

/**
* Erweitert das Verhalten der Implementation der Klasse
* Textdatei, bzw. seiner Implementation in der Klasse
* KonkreteTextdatei, um das Komprimieren der Datei.
*/
@Override
public String speichern(String dateiname) {
/*
* Reicht die Operation an den nächsthöheren Decorator
* in der Hierarchie weiter und wendet anschließend das
* erweiterte Verhalten (komprimieren) an.
*/
return komprimieren(textdatei.speichern(dateiname));
}

/**
* Beispielhafte Erweiterung des Verhaltens der Klasse
* Textdatei.
*/
private String komprimieren(String dateiname) {
/*
* Hier findet das erweiterte Verhalten statt, in diesem
* Fall z.B. das Komprimieren des Datenbankinhalts.
*/
System.out.println("Datei komprimiert!");
return dateiname;
}
}

Das Klassenmodell, so wie es im Diagramm zu sehen ist, ist hiermit beispielhaft implementiert.
Um das Verhalten dieses Codes zu testen, schreiben wir uns eine ausführbare Klasse, die die Klassen instanziiert und verwendet:

package de.theserverside.designpatterns.decorator;

/**
* Testet der Decorator-Klassen.
*/
public class VerwendeTextdateiDekorierer {
public static void main(String[] args) {
/*
* Erzeugt eine Textdatei und wrappt die Referenz auf
* diese in Objekte, die sie um Verhalten erweitern.
*/
Textdatei textdatei = new KonkreteTextdatei();
textdatei = new EntitiesUmgewandeltTextdateiDekorierer(
textdatei);
textdatei = new KomprimierteTextdateiDekorierer(
textdatei);
textdatei.speichern("MeinDateiname.txt");
}
}

Das Ergebnis des Aufrufs dieser Klasse sieht so aus:
[text]Daten gespeichert!
HTML-Entities in der Datei umgewandelt!
Datei komprimiert!
[/text]

Wie hier zu sehen ist, findet die Ausgabe in den Zeilen 1-3 in der Reihenfolge statt wie die Objekte in VerwendeTextdateiDekorierer gewrappt worden sind. Daraus ist schnell ersichtlich, dass der Aufruf immer, ganz nach Java-Aufrufmethodik, erst an die höherimplementierte Methode weitergereicht wird.

Sourcecode zum Artikel

Sourcecode: Decorator Pattern in Java

Weiterführende Literatur

Wer sich mit Design Patterns näher auseinandersetzen will, kommt um das Standardwerk der „Gang of Four“ (GoF) „Design Patterns“ nicht herum. Der Link führt zur englischen Ausgabe des Buches, da die deutsche Übersetzung sehr grausam ist…

Wenn Ihnen dieses Buch zu trocken ist und Sie leichter verdaubares Material suchen, sind die beiden folgenden Bücher zum Thema „Design Patterns“ bzw. „Design Patterns in Java“ auch sehr zu empfehlen:

Eine unterhaltsame und sehr leicht verständliche Einführung in die Thematik Design Patterns in Java finden Sie in „Entwurfsmuster von Kopf bis Fuß„. Das gesamte Buch ist bebildert und anschaulich gestaltet, und die Konzepte werden didaktisch hervorragend erläutert. Die Codebeispiele sind in Java geschrieben und auf das Nötigste reduziert.

Das Buch „The Design Patterns Java Workbook“ arbeitet die Design Patterns der Reihe nach ab, aber nicht, ohne vorher die Grundlagen (Interfaces, Abstrakte Klassen etc.) zu erläutern. Jedes Kapitel enthält Aufgaben, die gelöst werden können und anhand derer das Erlernte sofort praktisch angewandt werden kann. Da bisher keine deutsche Übersetzung existiert (Stand: September 2009), müssen Sie noch mit der englischen Fassung vorlieb nehmen.



Kommentieren

Weitere Empfehlungen:




-->

Java - Jul 19, 2011 20:30 - 0 Kommentare

Eine (wirklich gute) Einführung in Maven

Mehr Artikel der Kategorie Softwarearchitekturen


Datenbanken, Internet Technologien - Nov 19, 2009 9:24 - 0 Kommentare

Oracle WebServices im praktischen Einsatz

More In Datenbanken


Betriebssysteme - Jul 27, 2006 22:28 - 0 Kommentare

Angriff der Klone!

More In Betriebssysteme