Programmieren mit Java IIhttp://sol.cs.hm.edu/4129Inhaltsverzeichnis
C. Beispiel Decorator-Pattern
Pizzas mit Boden und Auflagen
Das Decorator-Pattern eignet sich als Lösungsansatz für ganz unterschiedliche Problemstellungen. In diesem Anhang soll das Muster an einem konkreten Beispiel veranschaulicht werden, das nichts mit Ein- und Ausgabe zu tun hat.
Ein moderner Pizzadienst möchte seinen Betrieb mit Software unterstützen. Als Teil dieses Projekts sollen die verschiedenen Pizzas im Angebot durch Objekte modelliert werden. An diesem Beispiel wird das Decorator-Pattern plastisch umgesetzt.
Interface für alle Bausteine
Gemeinsame Eigenschaften aller Pizzas sind die Größe, der Preis sowie die Angaben, ob sie vegetarisch und ob sie scharf sind. Entsprechende Methoden werden in einem Interface festgelegt:
public interface Pizza {

int getPrice();



boolean isVegetarian();



boolean isHot();

}

Pizza.java: Interface Pizza.
Grundlage und Dekoratoren
Als konkrete Bausteine dienen Pizzaböden, die einfachsten möglichen Pizzas. Die gemeinsamen Eigenschaften von Pizzaböden (Preis, scharf oder nicht) fasst die abstrakte Basisklasse[1]
Die Pizzaböden in diesem einfachen Beispiel sind sich so ähnlich, dass sogar ein Aufzählungstyp ausreichen würde.
Base zusammen. Alle Pizzaböden sind vegetarisch, Preis und Schärfe werden jeweils im Konstruktor festgelegt:
public abstract class Base implements Pizza {

private final int price;



private final boolean hot;



public Base(int price, boolean hot) {

this.price = price;

this.hot = hot;

}



public int getPrice() {

return price;

}



public boolean isHot() {

return hot;

}



public boolean isVegetarian() {

return true;

}

}

Base.java: Aufzählungstyp als konkrete Komponentenklasse: Pizzaböden.
Von Base sind drei Varianten von Pizzaböden abgeleitet:
public class Crunchy extends Base {

public Crunchy() {

super(300, false);

}

}

Crunchy.java: Knuspriger Pizzaboden.
public class Puffy extends Base {

public Puffy() {

super(400, false);

}

}

Puffy.java: Weicher, dicker Pizzaboden.
public class Sicilian extends Base {

public Sicilian() {

super(350, true);

}

}

Sicilian.java: Scharfer Pizzaboden.
Abstrakter Dekorator
Die verschiedenen Auflagen werden als Dekoratoren implementiert. Die gemeinsame Eigenschaft aller Auflagen, die Verwaltung der darunterliegenden Pizza, wird in eine gemeinsame abstrakte Basisklasse Topping herausgezogen. Topping implementiert das Interface Pizza und enthält eine Objektvariable vom Typ Pizza, die die darunterliegende restliche Pizza ohne diese Auflage repräsentiert. Die Klasse ist abstrakt, obwohl sie keine abstrakten Methoden enthält. Damit können keine Instanzen erzeugt werden. Alle Methoden des Interface werden an die Objektvariable delegiert.
public abstract class Topping implements Pizza {

private final Pizza below;



public Topping(Pizza below) {

this.below = below;

}



public boolean isVegetarian() {

return below.isVegetarian();

}



public boolean isHot() {

return below.isHot();

}



public int getPrice() {

return below.getPrice();

}

}

Topping.java: Abstrakte Dekoratorklasse: Pizzaauflage.
Konkrete Dekoratoren
Von der abstrakten Basisklasse Topping können verschiedene konkrete Dekoratoren abgeleitet werden, die die ererbten Methoden nach Bedarf redefinieren
Jede Auflage Käse erhöht den Preis der gesamten Pizza um 1 Euro. Sie ändert nichts an den Eigenschaften vegetarisch und scharf. Die aus der Basisklasse ererbten Methoden isVegetarian und isHot rufen die entsprechenden Eigenschaften der restlichen Pizza, ohne diesen Käse, ab und geben das Ergebnis an den Aufrufer zurück. Beim Preis wird der Preis dieser Käseauflage (1 Euro) zum Preis der übrigen Pizza addiert und die Summe als Preis der ganzen Pizza (dieser Käse mit allem anderen) zurückgegeben.
public class Cheese extends Topping {

public Cheese(Pizza below) {

super(below);

}



public int getPrice() {

return 100 + super.getPrice();

}

}

Cheese.java: Konkrete Dekoratorklasse: Käse als Pizzaauflage.
Salami als Auflage kostet 1,50 Euro. Eine Pizza mit Salami ist nicht vegetarisch, unabhängig vom darunterliegenden Rest der Pizza. Die Methode isVegetarian ruft deshalb die ererbte Methode nicht auf, sondern gibt sofort und ohne weitere Rückfrage das Ergebnis false zurück. Was immer unter dieser Salami liegt, die ganze Pizza kann wegen dieser Salami nicht vegetarisch sein.
public class Salami extends Topping {

public Salami(Pizza below) {

super(below);

}



public int getPrice() {

return 150 + super.getPrice();

}



public boolean isVegetarian() {

return false;

}

}

Salami.java: Konkrete Dekoratorklasse: Salami als Pizzaauflage.
Gewürze sind kostenlos, machen eine Pizza aber scharf.
public class Chili extends Topping {

public Chili(Pizza below) {

super(below);

}



public boolean isHot() {

return true;

}

}

Chili.java: Konkrete Dekoratorklasse: Gewürze als Pizzaauflage.
Grundstruktur des Decorator-Pattern
Das folgende Diagramm zeigt die Beziehungen zwischen den Typen. Darin lässt sich die Grundstruktur des Decorator-Patterns erkennen (Seite ). image/svg+xml Pizza {interface} Base {abstract} Topping {abstract} Cheese Salami 1 -below: Pizza Chili -price: int-hot: boolean Crunchy Puffy Sicilian
Aufbau einer Objektstruktur zur Laufzeit
Eine Anwendung kann aus diesen Typen zur Laufzeit beliebige Pizzas zusammenstellen und deren Eigenschaften abfragen. Das folgende Programm erwartet auf der Kommandozeile eine Reihe von Kürzeln mit jeweils den beiden kleinen Anfangsbuchstaben der gewünschten Bausteine. Als Erstes muss ein Pizzaboden genannt werden, daran anschließend die Auflagen.
public class PizzaMain {

public static void main(String... args) {

Pizza pizza = null;

for(String arg: args)

if(pizza == null)

switch(arg) {

case "Crunchy":

pizza = new Crunchy();

break;

case "Puffy":

pizza = new Puffy();

break;

case "Sicilian":

pizza = new Sicilian();

break;

}

else

switch(arg) {

case "Cheese":

pizza = new Cheese(pizza);

break;

case "Salami":

pizza = new Salami(pizza);

break;

case "Chili":

pizza = new Chili(pizza);

break;

}

System.out.printf("Your pizza:%nprice: %d%nvegetarian: %b%nhot: %b%n",

pizza.getPriceVerbose(""),

pizza.isVegetarian(),

pizza.isHot());

}

}

PizzaMain.java: Programm, das eine Pizza nach Kommandozeilenargumenten zusammensetzt und ihre Eigenschaften ausgibt.
Beim Start des Programms können beliebige Pizzas zusammengestellt werden. Im folgenden Beispiel wird ein Pizzaboden der Art Crunchy mit zweimal Käse, einmal Salami und einmal Gewürzen belegt. Das Ergebnis ist eine Pizza mit einem Gesamtpreis von 6,50 Euro, die nicht vegetarisch, aber scharf ist:
$ java PizzaMain Crunchy Cheese Cheese Salami Chili

Your pizza:

price: 650

vegetarian: false

hot: true
Rekursive Methodenaufrufe
Die folgende Ausgabe macht die Aufrufe der getPrice-Methoden im obigen Beispiel sichtbar. In spitzen Klammern ist jeweils die Objektreferenz angegeben, anhand derer gleiche und verschiedene Objekte identifiziert werden können.[2]
Der Wert des Codes ist ohne Bedeutung. Er wird von der toString-Methode geliefert, die in Object definiert ist. Es handelt sich um die hexadezimale Darstellung des Hashcodes, der auf der Speicheradresse des Objekts beruht.
Die Aufrufkette beginnt mit dem vordersten Dekorator, einem Chili-Objekt. Chili definiert keine eigene getPrice-Methode, sondern erbt sie von der Basisklasse. Das Topping-Objekt, dessen getPrice-Aufruf als Erstes protokolliert wird, delegiert den Aufruf an den nächsten Dekorator, das Salami-Objekt, das wiederum die Basisklassenmethode aufruft. Die Aufrufkette endet beim Base-Objekt, das ohne weitere Aufrufe den eigenen Preis, 300, zurückliefert. Bei der Rückkehr addieren die konkreten Auflagen ihren eigenen Preis zum Preis der restlichen Pizza und geben die Summe zurück:
$ java PizzaMain Crunchy Cheese Cheese Salami Chili

Topping@385715.getPrice() =>

Salami@dd23cf.getPrice() =>

Topping@dd23cf.getPrice() =>

Cheese@5a25f3.getPrice() =>

Topping@5a25f3.getPrice() =>

Cheese@717ef5.getPrice() =>

Topping@717ef5.getPrice() =>

Base@1461c98.getPrice() =>

<= Base@1461c98.getPrice(): 300

<= Topping@717ef5.getPrice(): 300

<= Cheese@717ef5.getPrice(): 400

<= Topping@5a25f3.getPrice(): 400

<= Cheese@5a25f3.getPrice(): 500

<= Topping@dd23cf.getPrice(): 500

<= Salami@dd23cf.getPrice(): 650

<= Topping@385715.getPrice(): 650
Den Bausteinen des Decorator-Patterns sind die folgenden Typen der Pizza-Implementierung zugeordnet:
AbstractComponent
Pizza
ConcreteComponent
Base, Crunchy, Puffy, Sicilian
AbstractDecorator
Topping
ConcreteDecorator
Cheese, Salami, Chili
Grenzen des Musters
So elegante Lösungen das Decorator-Pattern für bestimmte Probleme auch liefert, so hat es doch auch Grenzen: