Java - von am Thursday, 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.

  1. package de.theserverside.designpatterns.decorator;
  2.  
  3. /**
  4.  * Stellt im UML-Klassendiagramm "Component" dar, von der eine
  5.  * konkrete Klasse abgeleitet wird, die ihr Standardverhalten
  6.  * implementiert.
  7.  */
  8. public abstract class Textdatei {
  9.     public abstract String speichern(String dateiname);
  10. }

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:

  1. package de.theserverside.designpatterns.decorator;
  2.  
  3. /**
  4.  * Stellt im UML-Klassendiagramm "ConcreteComponent" dar. Sie
  5.  * implementiert auch das Standardverhalten der Komponente, in
  6.  * diesem Fall das Speichern der Datei. Sie wird später bei der
  7.  * Implementierung zusätzlichen Verhaltens in die entsprechenden
  8.  * Methoden "reingereicht".
  9.  */
  10. public class KonkreteTextdatei extends Textdatei {
  11.     /**
  12.      * Diese Methode speichert den Inhalt einer bestimmten
  13.      * Datenbankspalte in einer Datei und liefert den Namen der
  14.      * Datei zurück.
  15.      *
  16.      * @return Name der gespeicherten Datei
  17.      */
  18.     @Override
  19.     public String speichern(String dateiname) {
  20.         /*
  21.          * Hier steht der Code zum Abfragen der Datenbank und
  22.          * Speichern des Inhalts im Dateisystem.
  23.          */
  24.         System.out.println("Daten gespeichert!");
  25.         return dateiname;
  26.     }
  27. }

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.

  1. package de.theserverside.designpatterns.decorator;
  2.  
  3. /**
  4.  * Stellt im UML-Klassendiagramm "Decorator" dar, die
  5.  * Basisklasse aller Erweiterungen des Verhaltens der Klasse
  6.  * Textdatei ("Component").
  7.  */
  8. public abstract class TextdateiDekorierer extends Textdatei {
  9.     /**
  10.      * Stellt die Basisimplementation für das erweiterte
  11.      * Verhalten bereit.
  12.      */
  13.     @Override
  14.     public abstract String speichern(String dateiname);
  15. }

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

  1. package de.theserverside.designpatterns.decorator;
  2.  
  3. /**
  4.  * Stellt im UML-Klassendiagramm "ConcreteDecorator" dar, die
  5.  * das Verhaltens der Klasse Textdatei ("Component") erweitert.
  6.  */
  7. public class EntitiesUmgewandeltTextdateiDekorierer extends
  8.         TextdateiDekorierer {
  9.     private final Textdatei textdatei;
  10.  
  11.     /**
  12.      * Instanziiert die Klasse und sichert die Instanz auf den
  13.      * nächsthöheren Decorator in der Hierarchie.
  14.      */
  15.     public EntitiesUmgewandeltTextdateiDekorierer(
  16.             Textdatei textdatei) {
  17.         this.textdatei = textdatei;
  18.     }
  19.  
  20.     /**
  21.      * Erweitert das Verhalten der Implementation der Klasse
  22.      * Textdatei, bzw. seiner Implementation in der Klasse
  23.      * KonkreteTextdatei, um das Umwandeln der Sonderzeichen.
  24.      */
  25.     @Override
  26.     public String speichern(String dateiname) {
  27.         /*
  28.          * Reicht die Operation an den nächsthöheren Decorator
  29.          * in der Hierarchie weiter und wendet anschließend das
  30.          * erweiterte Verhalten (umwandeln) an.
  31.          */
  32.         return umwandeln(textdatei.speichern(dateiname));
  33.     }
  34.  
  35.     /**
  36.      * Beispielhafte Erweiterung des Verhaltens der Klasse
  37.      * Textdatei.
  38.      */
  39.     private String umwandeln(String dateiname) {
  40.         /*
  41.          * Hier findet das erweiterte Verhalten statt, in diesem
  42.          * Fall z.B. das Umwandeln von Sonderzeichen in
  43.          * HTML-Entities. Ein "Ä" wird zum "Ä" etc..
  44.          */
  45.         System.out
  46.                 .println("HTML-Entities in der Datei umgewandelt!");
  47.         return dateiname;
  48.     }
  49. }

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

  1. package de.theserverside.designpatterns.decorator;
  2.  
  3. /**
  4.  * Stellt im UML-Klassendiagramm "ConcreteDecorator" dar, die
  5.  * das Verhaltens der Klasse Textdatei ("Component") erweitert.
  6.  */
  7. public class KomprimierteTextdateiDekorierer extends
  8.         TextdateiDekorierer {
  9.     private final Textdatei textdatei;
  10.  
  11.     /**
  12.      * Instanziiert die Klasse und sichert die Instanz auf den
  13.      * nächsthöheren Decorator in der Hierarchie.
  14.      */
  15.     public KomprimierteTextdateiDekorierer(Textdatei textdatei) {
  16.         this.textdatei = textdatei;
  17.     }
  18.  
  19.     /**
  20.      * Erweitert das Verhalten der Implementation der Klasse
  21.      * Textdatei, bzw. seiner Implementation in der Klasse
  22.      * KonkreteTextdatei, um das Komprimieren der Datei.
  23.      */
  24.     @Override
  25.     public String speichern(String dateiname) {
  26.         /*
  27.          * Reicht die Operation an den nächsthöheren Decorator
  28.          * in der Hierarchie weiter und wendet anschließend das
  29.          * erweiterte Verhalten (komprimieren) an.
  30.          */
  31.         return komprimieren(textdatei.speichern(dateiname));
  32.     }
  33.  
  34.     /**
  35.      * Beispielhafte Erweiterung des Verhaltens der Klasse
  36.      * Textdatei.
  37.      */
  38.     private String komprimieren(String dateiname) {
  39.         /*
  40.          * Hier findet das erweiterte Verhalten statt, in diesem
  41.          * Fall z.B. das Komprimieren des Datenbankinhalts.
  42.          */
  43.         System.out.println("Datei komprimiert!");
  44.         return dateiname;
  45.     }
  46. }

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:

  1. package de.theserverside.designpatterns.decorator;
  2.  
  3. /**
  4.  * Testet der Decorator-Klassen.
  5.  */
  6. public class VerwendeTextdateiDekorierer {
  7.     public static void main(String[] args) {
  8.         /*
  9.          * Erzeugt eine Textdatei und wrappt die Referenz auf
  10.          * diese in Objekte, die sie um Verhalten erweitern.
  11.          */
  12.         Textdatei textdatei = new KonkreteTextdatei();
  13.         textdatei = new EntitiesUmgewandeltTextdateiDekorierer(
  14.                 textdatei);
  15.         textdatei = new KomprimierteTextdateiDekorierer(
  16.                 textdatei);
  17.         textdatei.speichern("MeinDateiname.txt");
  18.     }
  19. }

Das Ergebnis des Aufrufs dieser Klasse sieht so aus:

  1. Daten gespeichert!
  2. HTML-Entities in der Datei umgewandelt!
  3. Datei komprimiert!

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.

Be Sociable, Share!


Kommentare

Kommentieren

Weitere Empfehlungen: