Das Decorator Design Pattern

Studienprojekt von Philipp Hauer. 2009 - 2010. ©

Inhalt

Einführung

Gegeben sei folgendes Szenario: Ein Restaurant möchte seine beiden beliebtesten Gerichte "Wiener Schnitzel mit Pommes" und "Wiener Schnitzel mit Bratkartoffeln" modellieren. Der Preis und die Beschreibung sollen via Methoden abgefragt werden (Beispiel in Anlehnung an [VKBF], Seite 80ff.).

Unser Praktikant schlägt eine schnelle Lösung vor: Die zwei Gerichte werden abgebildet, indem für jedes Gericht eine eigene Klasse erstellt wird, die das Gerichtinterface implementiert und dessen Methoden getPreis() und druckeBeschreibung() jeweils ausprogrammiert.

Decorator Design Pattern

Soweit so gut. Doch wie so oft in der Softwareentwicklung ändern sich die Anforderungen an unsere Modellierung: Das Restaurant möchte nun alle (!) Basisgerichte (Tofu, Garnelen, Hüftsteak, Wiener Schnitzel) mit allen möglichen Beilagen (Nudeln, Pommes, Bratkartoffeln, Salat, Suppe) modellieren. Bei unserer jetzigen Modellierung würde dies zu einer Klassenexplosion führen. 4 Basisgerichte * 5 Beilagen = 20 Klassen/Gerichte.

Decorator Design Pattern

Die Nachteile sind offensichtlich und absolut inakzeptabel:

  • Wartungs- und Erweiterungsarbeiten erfordern enormen Zeitaufwand und sind zudem fehleranfällig. Was ist, wenn eine neue Beilage hinzukommt? 4 neue Klassen müssen erstellt werden. Was, wenn ein Basisgericht teurer wird? Der bestehende Code von 5 Klassen muss angepasst werden. Dass es dabei schnell zu Fehlern und damit Inkonsistenzen kommt, ist wahrscheinlich.
  • Mangelnde Flexibilität. Was ist, wenn ein Gast die doppelte Portion Bratkartoffeln möchte? Oder Suppe und Salat? Diese Fälle deckt unsere Modellierung nicht ab.
  • Mangelnde Dynamik. Es besteht keine Möglichkeit, Änderungen oder Modifikationen zur Laufzeit durchzuführen. Also Beilagen dynamisch hinzuzufügen und auch wieder zu entfernen.

Doch bevor wir uns die Lösung anschauen, holen wir etwas weiter aus. Dazu sei hier ein wichtiges OO-Entwurfsprinzip rezitiert:

Offen/Geschlossen-Prinzip (Open/Closed):
Entwürfe sollten für Erweiterungen offen, aber für Veränderungen geschlossen sein.

Gemeint ist, dass Erweiterungen (neue Verhalten etc.) ohne Änderungen an bestehenden Code in das System integriert werden können. Dies im Hinterkopf behaltend erinnern wir uns an ein weiteres stets aktuelles OO-Prinzip:

 Identifiziere jene Aspekte, die sich ändern und trenne sie von jenen, die konstant bleiben.

Was ist konstant? Jedes Basisgericht und jede Beilage für sich.
Was ist variabel? Die Kombination von Basisgericht und Beilage.

Warum also nicht für jedes Basisgericht und für jede Beilage eine einzelne Klasse schreiben und diese dann dynamisch miteinander kombinieren?

Decorator Design Pattern

Es zeichnet sich ab, dass das Offen/Geschlossen-Prinzip passt: Unsere Basisgericht- und Beilagenklassen sollen konstant sein und damit für Veränderung geschlossen, allerdings kann jedes Basisgericht durch diverse Beilagen erweitert werden und ist somit für Erweiterung offen. Das war jetzt sehr theoretisch. Wie realisieren wir dies?

Dazu begreifen wir Beilagen als "Wrapper", als Hülle, die wir um ein Basisgericht legen. Es sind Objekte, die ein Gericht (beispielweise eine Basisgericht) besitzen. also eine Instanzvariabale auf ein solches besitzen. Soll eine Salatbeilage seinen Preis ausgeben, so fragt er zuerst sein Hüftsteak nach dessen Preis und addiert anschließend seinen eigenen Salatpreis hinzu.

getPreis() von Salat delegiert die Preisberechnung an sein (Basis)Gericht und fügt seinen eigenen Preis dazu:

//Salatcode 
private IGericht gericht; 

public double getPreis() { 
    return gericht.getPreis() + 2.25; 
} 

Auch soll es möglich sein, Beilagen beliebig ineinander zu schachteln (Hüftsteak mit Nudeln und Salat). Folgendes Schema zur Visualisierung dieses Vorgangs:

Decorator Design Pattern

Der Trick, den wir nun nutzen, ist, dass unsere Beilagen vom selben Typ sind, wie die Basisgerichte: IGericht. Logisch: ein Basisgericht und eine Beilage zusammen sind immer noch ein Gericht. Beilagen erweitern somit ebenso das Interface IGericht. Damit ist es möglich, dass eine Beilage sowohl ein Basisgericht als auch eine andere Beilage einpacken kann (diese wiederum ein anderes Gericht...).

Decorator Design Pattern

Damit wäre das Decorator Pattern prinzipiell schon realisiert. Nur ist es üblich und zweckmäßig zwischen dem Interface IGericht und den konkreten Beilagen noch eine abstrakte Beilagenklasse zu schalten, die generischen Code für alle Beilagen enthalten kann. In unserem Fall wäre dies die Deklaration der Instanzvariable auf ein IGericht, sowie Methoden zum Setzen dieser Instanzvariable (Setter oder Konstruktor).

Decorator Design Pattern

Quellcode Gerichtinterface:

public interface IGericht { 
    public double getPreis(); 
    public void druckeBeschreibung(); 
}  	
Quellcode konkrete Basisgerichte:

class Hueftsteak implements IGericht { 
    public void druckeBeschreibung() { 
        System.out.print("Hüftsteak"); 
    } 
    public double getPreis() { 
        return 13.0; 
    } 
} 


class Tofu implements IGericht { 
    public void druckeBeschreibung() { 
        System.out.print("Tofu"); 
    } 
    public double getPreis() { 
        return 8.50; 
    } 
} 


class Garnelen implements IGericht { 
    public void druckeBeschreibung() { 
        System.out.print("Garnelen"); 
    } 
    public double getPreis() { 
        return 13.50; 
    } 

} 


class WienerSchnitzel implements IGericht { 
    public void druckeBeschreibung() { 
        System.out.print("WienerSchnitzel"); 
    } 
    public double getPreis() { 
        return 10.50; 
    } 
} 			
Quellcode abstrakte Beilage:

public abstract class ABeilage implements IGericht { 
    protected IGericht gericht; 

    public ABeilage(IGericht pIGericht) { 
        gericht = pIGericht; 
    } 
}  			
Quellcode konkrete Beilagen:

class Pommes extends ABeilage { 
    public Pommes(IGericht pIGericht) { 
        super(pIGericht); 
    } 
    public void druckeBeschreibung() { 
        gericht.druckeBeschreibung(); 
        System.out.print(", Pommes"); 
    } 
    public double getPreis() { 
        return gericht.getPreis() + 2.50; 
    } 
} 


class Salat extends ABeilage { 
    public Salat(IGericht pIGericht) { 
        super(pIGericht); 
    } 
    public void druckeBeschreibung() { 
        gericht.druckeBeschreibung(); 
        System.out.print(", Salat"); 
    } 
    public double getPreis() { 
        return gericht.getPreis() + 2.25; 
    } 
} 


class Nudeln extends ABeilage { 
    public Nudeln(IGericht pIGericht) { 
        super(pIGericht); 
    } 
    public void druckeBeschreibung() { 
        gericht.druckeBeschreibung(); 
        System.out.print(", Nudeln"); 
    } 
    public double getPreis() { 
        return gericht.getPreis() + 4.50; 
    } 
} 


class Suppe extends ABeilage { 
    public Suppe(IGericht pIGericht) { 
        super(pIGericht); 
    } 
    public void druckeBeschreibung() { 
        gericht.druckeBeschreibung(); 
        System.out.print(", Suppe"); 
    } 
    public double getPreis() { 
        return gericht.getPreis() + 1.50; 
    } 
} 


class Bratkartoffeln extends ABeilage { 
    public Bratkartoffeln(IGericht pIGericht) { 
        super(pIGericht); 
    } 
    public void druckeBeschreibung() { 
        gericht.druckeBeschreibung(); 
        System.out.print(", Bratkartoffeln"); 
    } 
    public double getPreis() { 
        return gericht.getPreis() + 1.50; 
    } 
}  			

Nun können wir unsere Basisgerichte beliebig in Beilagen einpacken:

Beispielclient:

public static void main(String[] args) { 
    IGericht gericht = new Salat(new Nudeln(new Hueftsteak())); 
    gericht.druckeBeschreibung(); 
    //Hüftsteak, Nudeln, Salat 
    System.out.println(" für "+gericht.getPreis() + " Euro"); 
    // für 19.75 Euro 

    gericht = new Suppe(gericht); 
    gericht.druckeBeschreibung(); 
    //Hüftsteak, Nudeln, Salat, Suppe 
    System.out.println(" für "+gericht.getPreis() + " Euro"); 
    // für 21.25 Euro 
} 

Fassen wir die Vorteile unseres neuen Designs zusammen:

  • Flexibilität und Dynamik. Wir können unsere Basisgerichte nun (mehrfach und doppelt!) mit verschiedenen Beilagen umhüllen und damit ihr Verhalten flexibel und dynamisch (also auch zur Laufzeit) modifizieren.
  • Erweiterbarkeit und Robustheit. Wir haben ein System geschaffen, dass geschlossen für Veränderungen, aber offen für Erweiterungen ist. Das bedeutet, dass neue Beilage/Basisgerichte ohne Codeänderungen bestehender Basisgerichte und Beilagen ins System integriert werden können. Die Realisierung des Offen/Geschlossen-Prinzips erhöht die Wiederverwendbarkeit und Flexibilität unseres Designs.
  • Konsistenz und Wartbarkeit. Wir vermeiden im Vergleich zur statischen Vererbungslösung Coderedundanzen. So müssen wir das Verhalten eines jeden Gerichts (Preis, Beschreibung) nur einmal implementieren. Damit sinkt auch die Anfälligkeit für Fehler und Inkonsistenzen. Wartungs- und Erweiterungsarbeiten sind schneller durchgeführt, da nur eine Klasse angepasst werden.

Es zeigt sich, dass durch den Einsatz des Decorator Patterns, ein hohes Maß an Flexibilität und Dynamik gewonnen wird, während zeitgleich die Wartung erleichtert wird und Erweiterungen schnell und unkompliziert möglich sind.

Nach dieser Einführung wird im folgenden Abschnitt das Decorator Design Pattern formalisiert, näher analysiert und diskutiert.

Das Gerichtbeispiel mit Decorator Pattern Termini

Klasse Decorator Teilnehmer
Basisgerichte (Hüftsteak, Wiener Schnitzel etc.) Components (dt. Komponente)
Beilagen (Nudeln, Salat, Suppe etc.) Decorators (dt. Dekorierer)

Analyse und Diskussion

Gang Of Four-Definition

Decorator:
"Erweitere ein Objekt dynamisch um Zuständigkeiten. Dekorierer bieten eine flexible Alternative zur Unterklassenbildung, um die Funktionalität einer Klasse zu erweitern."
([GoF], Seite 199)

Beschreibung

Decorator Design PatternDas Decorator Design Pattern ermöglicht das dynamische Hinzufügen von Fähigkeiten zu einer Klasse. Dazu wird die Klasse, dessen Verhalten wir erweitern möchten (Component, Komponente), mit anderen Klassen (Decorator, Dekorierer) dekoriert (vgl. engl. "to wrap": umhüllen). Das heißt der Decorator umschließt (enthält) die Component. Der Decorator ist vom selben Typ wie das zudekorierende Objekt, hat somit die gleiche Schnittstelle und kann an der selben Stelle wie die Component benutzt werden. Er delegiert Methodenaufrufe an seine Component weiter und führt sein eigenes Verhalten davor oder danach aus.

Eine Component kann mit beliebig vielen Decorators dekoriert werden, um so seine Fähigkeiten immer weiter auszubauen.

Realisiert wird das Pattern beginnend mit einer abstrakten Superklasse (oder einem Interface) AComponent. Sowohl die konkreten Components als auch alle Decorators sind von diesem Typ. AComponent definiert weiterhin beliebig viele abstrakte Methoden, die alle konkreten Subklassen implementieren müssen (hier operate()). Die konkreten Components erweitern AComponent direkt und implementieren ihr Basisverhalten. Die abstrakte Klasse ADecorator erweitert AComponent ebenfalls und hält zugleich eine Referenz auf ein (beliebiges) Objekt vom Typ AComponent. Konkrete Decorators erweitern schließlich ADecorator (sind damit auch AComponents) und implementieren ihr Zusatzverhalten (operate()). Darin rufen sie zum einen die Methode(n) (operate()) der AComponent (geerbt von ADecorator) auf und fügen davor oder danach ihr decoratorspezifisches Verhalten hinzu. Da es keine Rolle spielt, ob ihre AComponentreferenz nun eine konkrete Component oder wiederum ein anderer Decorator ist, können Decorators beliebig geschachtelt werden.

Decorator Design Pattern

AComponent:

public abstract class AComponent { 
    //abstrakte Klasse oder Interface je nach Bedarf. 
    public abstract void operate(); 
} 			
ConcreteComponentA und ConcreteComponentB:

public class ConcreteComponentA extends AComponent { 
    public void operate() { 
        System.out.println("ConcreteComponentA operates."); 
    } 
} 

class ConcreteComponentB extends AComponent { 
    public void operate() { 
        System.out.println("ConcreteComponentB operates."); 
    } 
}  			
ADecorator:

public abstract class ADecorator extends AComponent { 
    protected AComponent component; 

    //Konstruktor zum komfortablen Initiieren am Client 
    public ADecorator(AComponent pAComponent) { 
        component = pAComponent; 
    } 
    //operate() wird nicht implementiert. 
}  			
ConcreteDecorator1, ConcreteDecorator2 und ConcreteDecorator3:

class ConcreteDecorator1 extends ADecorator { 
    public ConcreteDecorator1(AComponent pAComponent) { 
        super(pAComponent); 
    } 

    public void operate() { 
        //operate() der dekorierten Kompontente aufrufen vor 
        //oder nach der eigenen Funktionalitäst. 
        component.operate(); 
        //eigene Funktionalitäst: 
        System.out.println("ConcreteDecorator1 operates!"); 
    } 

} 

class ConcreteDecorator2 extends ADecorator { 
    //ConcreteDecorator2 fügt der Komponente einen neuen Zustand hinzu. 
    private int newState; 

    public ConcreteDecorator2(AComponent pAComponent) { 
        super(pAComponent); 
        newState = 999; 
    } 

    public void operate() { 
        component.operate(); 
        System.out.println("ConcreteDecorator2 operates with a new State: " + newState); 
    } 
} 

class ConcreteDecorator3 extends ADecorator { 
    public ConcreteDecorator3(AComponent pAComponent) { 
        super(pAComponent); 
    } 

    public void operate() { 
        component.operate(); 
        System.out.println("ConcreteDecorator3 operates with a new Operation: "+newOperation()); 
    } 

    //ConcreteDecorator3 fügt der Komponente eine neue Methode hinzu und 
    //erweitert seine Schnittstelle nach auÃüen. 
    public int newOperation() { 
        return (int)(Math.random() * 20); 
    } 
} 			
Beispielclient:

public static void main(String[] args) { 
    AComponent compA = new ConcreteComponentA(); 
    compA.operate(); 
    //ConcreteComponentA operates. 

    compA = new ConcreteDecorator1(compA); 
    compA.operate(); 
    //ConcreteComponentA operates. 
    //ConcreteDecorator1 operates! 

    AComponent compB = new ConcreteDecorator3(new ConcreteDecorator2(new ConcreteComponentB())); 
    compB.operate(); 
    //ConcreteComponentB operates. 
    //ConcreteDecorator2 operates with a new State: 999 
    //ConcreteDecorator3 operates with a new Operation: 12 
} 			

Anwendungsfälle

  • Zur dynamischen und transparenten Funktionserweiterung von Objekten.
    • Bei der Speicherung von Textdaten können zusätzliche Features, wie das Maskieren von Umlauten oder ein Komprimierungsalgorithmus hinzugeschaltet werden ([Sherzad]).
    • Eine Textkomponente kann mit einer Scollbar oder einem Rahmen dekoriert werden ([GoF], Seite 200). Siehe Anwendung in der Java-API.
  • Zum Hinzufügen, aber auch Entfernen von Funktionalitäten
    • Zu einer Anfrage setzbare Filter können nach dem Decorator Pattern modelliert und damit beliebig hinzugefügt oder entfernt werden ([PK], Seite 61).
  • Wenn Funktionalitätserweiterung mittels Vererbung impraktikabel ist:
    Dies ist zum einen der Fall bei voneinander unabhängigen Erweiterungen, wenn unter Beachtung jeder möglichen Erweiterungskombination eine schier unüberschaubare Anzahl von Klassen entstehen würde.
    • Das klassische Kaffeebeispiel: Es sind 3 Kaffeegrundsorten (Espresso, Edelröstung, Cappuccino, Latte Macchiato) und 4 optionale Zusätze (Milch, Schoko, Zucker, Sahne) gegeben. Das ergibt 16 Subklassen! Unüberschaubar, unwartbar und unflexibel ([VKBF], Seite 80ff).
    Zum anderen wenn die Klassendefinition versteckt oder nicht ableitbar (final!) ist. Das Decorator Pattern kann damit eine Möglichkeit bieten, finale Klassen um Funktionalitäten zu erweitern!

Weitere anschauliche Beispiele:

    • Tuning verschiedener Autotypen mit verschiedenen Features (Tieferlegung, Spoiler, Chip) ([Langner]).
    • Gericht mit diversen Beilagen ([Pras]).
    • Verschiedene Telefontypen (Handy, klassisches Telefon) mit variablen Verhalten (Vibration, Klingeln, Lautlos, Leuchten) ([GruntzD]).

Vorteile

  • Dynamik und Flexibilität. Klassen können ohne statische Vererbung um Verhalten erweitert werden - und das sowohl zur Kompilier- als auch zur Laufzeit. Beliebige Kombinationen (auch Mehrfachkombinationen) von Decorators sind möglich. Sowohl Decorators als auch Components sind frei variierbar und wiederverwendbar. Weiterhin kann bei der Fähigkeitserweiterung durch Dekorieren die Schnittstelle der Component nach außen erweitert werden (vgl. BufferedReader mit readLine()).
  • Vermeidung von langen unübersichtlichen Vererbungshierarchien.
  • Transparenz. Einem Client, der mit einer Component arbeitet, kann eine dekorierte Component "untergeschoben" werden, ohne dass sein Code bricht. Der Client wird wiederverwendbar.
  • Performance. Man muss nur jene Funktionalitäten initialisieren (und die Kosten dafür tragen), die auch wirklich benötigt werden. Einmal initialisiert, steht die Funktionalität stets zur Verfügung. Dies ist ein großer Vorteil gegenüber einer komplexen Basisklasse, die alle möglichen Funktionen permanent bereit hält, Auswahl zwischen ihnen treffen muss und entsprechend Ressourcen verbraucht.
  • Wartbarkeit durch schlanke, kohäsive Klassen. Dank Decorator Pattern können überfrachtete unübersichtliche Basisklassen vermieden werden. Jeder Decorator repräsentiert genau eine Funktion und nichts anderes. Dadurch steigt die Klassenkohäsion und der entsprechende prägnante Code wird leichter wart- und erweiterbar.

Nachteile

  • Erschwerte Fehlerfindung. Fehler, die sich in den oft langen Aufrufketten von dekorierten Objekten verstecken, sind schwer zu finden.
  • Hohe Objektanzahl. Jedes zusätzliche Feature bedarf eines neuen Decoratorobjektes. Schnell kann die Anzahl der vielen kleinen, ähnlichen Objekten und ihr Initialisierungscode unübersichtlich werden. Eine Factory empfiehlt sich, um den Erstellungscode zu kapseln.
  • Unkomfortable, wortreiche API. Die entstandene Decorator-API, die der Client nutzen muss, um ein Objekt nach seinen Wünschen zu dekorieren, ist nicht einsteigerfreundlich, besonders, wenn das Know-How über das Decorator Pattern fehlt. Jeder, der die Programmiersprache Java gelernt hat und das erste Mal von der Java-IO-API samt unzähligen schachtelbaren Streams, Readern und Writern erschlagen wurde, kann dieses Problem nachvollziehen. Die Komplexität von Systemen steigt mit der Einführung des Decorator Patterns.
  • Keine Objektidentität zwischen Component und dekorierte Component. Obwohl der Client einen Decorator, der eine Component umhüllt, genauso behandelt wie eine undekorierte Component, handelt es sich dabei nicht um die gleichen Objekte. Clients die auf Objektidentität setzen, werden Fehler erzeugen.

Anwendung in der Java Standardbibliothek

In der Java API wird das Decorator Design Pattern an zahlreichen Stellen angewandt.

Swing GUI: Komponentenverschachtelung

An zahlreichen Stellen in GUI-Bibliotheken werden GUI-Komponenten dekoriert. Ein typisches Beispiel ist die JScrollPane von Swing.

Decorator Design Pattern JScrollPaneDecorator Design Pattern JScrollPane

Soll eine JTextArea oder ein JTree Scrollbalken erhalten, so muss man das Objekt nur einer JScrollPane als Konstruktorparameter übergeben und das JScollPane-Objekt stattdessen (es ist ja auch eine Component) auf den Container (JPanel/JFrame) hinzufügen. Soll nun die Component gezeichnet werden, so fügt das JScrollPane-Objekt dem JTextArea-Objekt das nötige Verhalten für Scrollbalken hinzu.

Nutzung des Decorators JScrollPane:

JTextArea textComponent = new JTextArea(12,20); 
JScrollPane scrollComponent = new JScrollPane(textComponent); 
add(scrollComponent); 
//oder add(new JScrollPane(new JTextArea(12,20)); 			

Java IO

Der berühmteste Nutzer des Decorator Design Patterns ist die Java IO. Hier sei stellvertretend die InputStream-Hierarchie vorgestellt. Bei OutputStream, Reader und Writer verhält es sich analog.

Decorator Design Pattern Java IO

Die konkreten Basisstreams (Concrete Components) können durch andere Streams beliebig dekoriert werden, um ihnen damit zusätzliche Funktionalität zuzufügen. So fügt ein BufferedInputStream dem Basisstream einen Buffer hinzu und ermöglicht damit performantes Lesen. Dieser kann wiederum weiteren Decoratorstreams übergeben werden bis das gewünschte Verhalten erreicht wurde.

Decorator Design Pattern IO

frei nach [VKBF], Seite 100

Interessant in der Java IO ist weiterhin der Decorator BufferedReader. Dieser Decorator implementiert nicht nur das Readerinterface (Componentinterface), sondern erweitert diese Schnittstelle noch um die Methode readLine(), mit der eine ganze Zeile statt einzelnen Zeichen gelesen werden kann. Um diese Methode nutzen zu können, muss der Client natürlich wissen, dass er es mit einem BufferedReader zu tun hat und nicht nur mit irgendeinen Reader.

Dekorierende Collections der Utilityklasse Collections

Auch die Utilityklasse Collections nutzt das Decorator Pattern. Genau genommen, wird das Pattern von deren static nested ("inneren") Klassen, wie SynchronizedList oder UnmodifiableList genutzt. Diese Klassen, die selber das Listinterface implementieren (und damit selber Lists sind), nehmen eine List als Konstrukturparameter und leiten die entsprechenden Listmethoden an ihre List weiter, nehmen dabei jedoch Modifikationen vor.

UnmodifiableList leitet nur lesende Zugriffe (get(), hashCode(), indexOf()) an seine dekorierte List weiter. Auf alle Versuche schreibend auf die Liste zuzugreifen (add(), remove(), addAll()) wird eine UnsupportedOperationException geworfen.

SynchronizedList synchronisiert alle Zugriffe auf die List.

Codeausschnitt der static nested Klasse UnmodifiableList:

//viel Code... 
static class UnmodifiableList<E> extends UnmodifiableCollection<E> implements List<E> { 

    final List<? extends E> list; 

    UnmodifiableList(List<? extends E> list) { 
        super(list); 
        this.list = list; 
    } 

    public int hashCode() { 
        return list.hashCode(); 
    } 
    public E get(int index) { 
        return list.get(index); 
    } 
    public E set(int index, E element) { 
        throw new UnsupportedOperationException(); 
    } 
    public void add(int index, E element) { 
        throw new UnsupportedOperationException(); 
    } 
    public E remove(int index) { 
        throw new UnsupportedOperationException(); 
    } 
    public int indexOf(Object o) { 
        return list.indexOf(o); 
    } 
    public int lastIndexOf(Object o) { 
        return list.lastIndexOf(o); 
    } 
    public boolean addAll(int index, Collection<? extends E> c) { 
        throw new UnsupportedOperationException(); 
    } 
    //viel Code... 
}  			
Codeausschnitt der static nested Klasse SynchronizedList:

//viel Code.... 
static class SynchronizedList<E> extends SynchronizedCollection<E> implements List<E> { 

    final List<E> list; 

    SynchronizedList(List<E> list) { 
        super(list); 
        this.list = list; 
    } 

    public boolean equals(Object o) { 
        synchronized (mutex) { 
            return list.equals(o); 
        } 
    } 
    public int hashCode() { 
        synchronized (mutex) { 
            return list.hashCode(); 
        } 
    } 
    public E get(int index) { 
        synchronized (mutex) { 
            return list.get(index); 
        } 
    } 
    public void add(int index, E element) { 
        synchronized (mutex) { 
            list.add(index, element); 
        } 
    } 
    public E remove(int index) { 
        synchronized (mutex) { 
            return list.remove(index); 
        } 
    } 
    public int indexOf(Object o) { 
        synchronized (mutex) { 
            return list.indexOf(o); 
        } 
    } 
    //viel Code... 
} 			

Kommentare

Bitte auswählen:*
Jens L. 2016-03-07 14:37:48
Hi, eine sehr schöne Beschreibung und Erklärung. Auch die Beispiele sind immer schön gewählt.
In diesem konkreten Beispiel hätte ich aber eine Anmerkung:

Laut dem UML Diagramm und so wie ich es verstanden habe, sollen ABeilagen IGerichte umhüllen/ dekorieren.

Rein formal müsste doch aber auch Folgendes funktionieren:

Nudeln nudelnMitPommes = new Nudeln(new Pommes(null)); // vorausgesetzt es gibt diesen parametrisierten Konstruktor
/* alternativ:
Nudeln nudelnMitPommes = new Nudeln();
nudelnMitPommes.setBasisgerich t(new Pommes());
*/

Folgender Befehl führt doch dann zu einer NullPointerException:
nudelnMitPommes.getPreis();

Nudeln würden den eigenen Preis mit dem Preis des Basisgerichtes addieren. Da Basisgericht ein IGericht ist und Pommes ebenso diese Schnittstelle implementiert, da es von ABeilagen erbt, darf es als Basisgericht für nudelnMitPommes eingesetzt werden. Nur besitzt ein neu erzeugtes Pommes Objekt keine Referenz auf ein Basisgericht.

Natürlich kann man die ABeilage.getPreis() Funktion dann so schreiben:

if(this.basisgericht == null) return 0; // Abfangen eines nicht gesetzen Basisgerichtes.
return this.preis() + this.basisgericht.getPreis(); /* Abwicklung der Preisberechnung. setzt voraus, dass preis ein Attribut von ABeilage ist. */

Dann würde aber immer noch nicht der Umstand gelöst werden, dass eine Beilage zwingend ein Basisgericht benötigt.
Gibt es da eine elegante Absicherung?


Oder habe ich grade einen Denkfehler?
Danke für die Antworten.
Benjamin W. 2016-02-03 16:00:50
Eine wirklich wunderbare und nützliche Seite :) - Ich musste beim "Salatcode" ein wenig schmunzeln, erinnert mich an Spagehtticode...
Irene 2014-12-02 21:42:14
Wunderbar verständliche Erklärung und sehr ansprechend gestaltet. Tolle Beispiele! Werde ich unbedingt weiterempfehlen!
Ich 2014-06-25 10:35:37
Hat uns sehr bei unserem Informatik-Referat geholfen. Vielen Dank für diesen schönen Artikel.
Johannes 2013-10-25 00:23:49
Sehr schönes einführendes Beispiel, schöne allgemeine Darstellung und letztendlich der Praxisbezug zur Java API. Dazu Diagramme und Quellcode!

Werde diese Seite weiterempfehlen :-)
Philipp 2013-08-07 14:56:00
Hallo Marius,
ich habe gerade kein UML-Buch zur Hand, aber für mich sind alle Ganz-Teil-Beziehungen (Aggregation, Komposition) immer gerichtet vom Ganzem zum Teil (außer ich Zeichne an beiden Seiten Rollen oder Multiplizitäten ein). Aber natürlich hast du Recht: Das muss nicht so sein. Nicht ohne Grund kann man ja auch einen Pfeil an die Beziehung einzeichnen. Ich benutze die Beziehung aber im gesamten Katalog so, daher würde ich es (erstmal) so lassen.
Dennoch freue ich mich, so aufmerksame Besucher zu haben. :-)
Philipp
Marius 2013-08-05 14:40:19
In deiner generischen UML Diagram hast Du einen kleinen Fehler, die Beziehung zwischen AComponent und ADekorator ist als Bidirektional dargestellt. Die Beziehung zwischen ADecorator und AComponent is aber unidirektional. ADecorator enthält eine AComponent Referenzierung aber AComponet hat keine ADecorator Referenzierung.
Simon 2013-05-10 07:47:38
Das Decorator Muster sieht wirklich vielversprechend aus. Wie kann ich den Instantiierungsprozess dynamisch gestalten, wenn ich auf Basis von Flags auswähle, welche Dekorierer ich verwende?

Gibt es generell ein Weg den Instantiierungsprozess, der auf Bedingungen basiert zu verbessern? Ich habe an Factory oder Buider gedacht, komme aber auf keine Lösung.

Vielleicht kann mir hier jemand helfen.
André 2013-02-02 11:46:15
Hallo Philipp,
mir fällt grad die Multiplizität von 1 auf. Dann macht das natürlich wieder sinn.
Vielen Dank für die Stellungnahme. Eine sehr gute Seite ist das hier :)
Philipp 2013-02-01 09:06:12
Hallo Andre,

nein, die Komposition ist richtig. Die Raute ist beim Ganzen (der Zutat). Schau dir das Pattern noch einmal genau an. Eine Zutat (was ja auch ein Gericht ist) hat eine Referenz auf ein anderes Gericht (was ein Basisgericht oder wieder eine Zutat sein kann).
André 2013-01-31 12:06:29
So wie ich das sehe, ist die Komposition nicht richtig. Ich kann mich irren aber wenn ein Gericht aus Zutaten aufgebaut ist, dann muss die schwarze Raute an dem Gericht sein, nicht an der Zutat.

würde gerne eine Internetseite posten aber das ist hier nicht möglich ;)
Daniel 2012-10-01 10:15:39
Hallo Philipp,

muss es nicht im decorator-def-kl.png eine ConcreteComponentB geben, statt zwei mal A ? ;-)

lg
Daniel

Ansonsten tolle Seite, super gemacht :-)
M.R 2012-08-28 16:34:03
Du könntest einen zweiten Konstrukter hinzufügen, der kein Gericht als Paramter erwartet...
Günther 2012-08-28 14:30:04
Frage: Über dieses Pattern ist es aber nicht möglich, NUR Pommes oder NUR Salat zu bestellen, oder?
Anonymous 2012-08-16 11:50:04
Hey tausend Dank!!!

Seite: 1 - nächste Seite