Menu
Coddy logo textTech

Decorator-Muster

Teil des Abschnitts Objektorientierte Programmierung der PHP-Journey von Coddy — Lektion 77 von 91.

Das Decorator-Pattern ist ein Strukturmuster, das es Ihnen ermöglicht, Objekten dynamisch neue Verhaltensweisen hinzuzufügen, indem Sie diese in spezielle Wrapper-Objekte, sogenannte Decorator, einhüllen. Im Gegensatz zur Vererbung, die Verhalten zur Kompilierzeit hinzufügt, fügen Decorator Verhalten zur Laufzeit hinzu.

Stellen Sie sich ein Café vor. Sie beginnen mit einem einfachen Kaffee und fügen dann Extras wie Milch, Zucker oder Schlagsahne hinzu.

Jede Ergänzung umhüllt den ursprünglichen Kaffee und erweitert dessen Beschreibung und Kosten. Die entscheidende Erkenntnis ist, dass sowohl der Basis-Kaffee als auch die Dekoratoren dieselbe Schnittstelle teilen.

<?php
interface Coffee {
    public function getDescription(): string;
    public function getCost(): float;
}

class SimpleCoffee implements Coffee {
    public function getDescription(): string {
        return "Simple Coffee";
    }
    
    public function getCost(): float {
        return 2.00;
    }
}

Dekoratoren implementieren dieselbe Schnittstelle und umschließen ein weiteres Coffee-Objekt:

<?php
class MilkDecorator implements Coffee {
    public function __construct(private Coffee $coffee) {}
    
    public function getDescription(): string {
        return $this->coffee->getDescription() . ", Milk";
    }
    
    public function getCost(): float {
        return $this->coffee->getCost() + 0.50;
    }
}

class SugarDecorator implements Coffee {
    public function __construct(private Coffee $coffee) {}
    
    public function getDescription(): string {
        return $this->coffee->getDescription() . ", Sugar";
    }
    
    public function getCost(): float {
        return $this->coffee->getCost() + 0.25;
    }
}

$coffee = new SimpleCoffee();
$coffee = new MilkDecorator($coffee);
$coffee = new SugarDecorator($coffee);

echo $coffee->getDescription() . " = $" . $coffee->getCost();

Ausgabe:

Simple Coffee, Milk, Sugar = $2.75

Jeder Decorator umhüllt das vorherige Objekt und bildet so eine Kette. Das Muster ermöglicht es Ihnen, Verhaltensweisen in beliebiger Reihenfolge und Menge zu kombinieren, ohne für jede mögliche Kombination eine Klasse erstellen zu müssen. Benötigen Sie doppelte Milch? Umhüllen Sie es einfach zweimal.

challenge icon

Aufgabe

Einfach

Lassen Sie uns ein Pizza-Bestellsystem mit dem Decorator-Pattern erstellen. Sie beginnen mit einer Basis-Pizza und fügen dynamisch Beläge hinzu — jeder Belag umhüllt die Pizza und erweitert deren Beschreibung und Preis, genau wie im Kaffee-Beispiel aus der Lektion.

Sie werden Ihren Code über vier Dateien organisieren:

  • Pizza.php — Definieren Sie ein Pizza-Interface, das den Vertrag für alle Pizzen und Beläge festlegt. Es sollte zwei Methoden haben: getDescription(): string und getPrice(): float.
  • BasePizza.php — Binden Sie das Pizza-Interface ein und erstellen Sie eine Klasse MargheritaPizza, die es implementiert. Dies ist Ihre Basis-Pizza mit der Beschreibung "Margherita" und einem Preis von 8.00.
  • Toppings.php — Binden Sie das Pizza-Interface ein und erstellen Sie drei Decorator-Klassen, die es implementieren. Jeder Decorator umhüllt ein Pizza-Objekt (nehmen Sie dieses im Konstruktor entgegen) und erweitert sowohl die Beschreibung als auch den Preis:
    • CheeseTopping — Fügt ", Extra Cheese" zur Beschreibung und 1.50 zum Preis hinzu
    • PepperoniTopping — Fügt ", Pepperoni" zur Beschreibung und 2.00 zum Preis hinzu
    • MushroomTopping — Fügt ", Mushrooms" zur Beschreibung und 1.25 zum Preis hinzu
  • main.php — Binden Sie die Dateien BasePizza und Toppings ein. Sie erhalten eine Eingabe: eine durch Kommas getrennte Liste von Belägen, die hinzugefügt werden sollen (z. B. "cheese,pepperoni" oder "mushroom,cheese,cheese").

    Beginnen Sie mit einer MargheritaPizza. Wickeln Sie Ihre Pizza für jeden Belag in der Eingabe mit dem entsprechenden Decorator ein:

    • "cheese" wird mit CheeseTopping umhüllt
    • "pepperoni" wird mit PepperoniTopping umhüllt
    • "mushroom" wird mit MushroomTopping umhüllt

    Geben Sie nach dem Hinzufügen aller Beläge die Beschreibung und den Preis der fertigen Pizza in diesem Format aus:

    [description] = $[price]

    Formatieren Sie den Preis auf zwei Dezimalstellen.

Beachten Sie, wie derselbe Belag mehrfach angewendet werden kann — die Bestellung von doppeltem Käse bedeutet einfach, die Pizza zweimal mit CheeseTopping zu umhüllen. Diese Flexibilität ist die Stärke des Decorator-Patterns!

Spickzettel

Das Decorator Pattern (Dekorierer-Muster) ist ein Strukturmuster, das Objekten zur Laufzeit dynamisch Verhalten hinzufügt, indem es sie in Dekorierer-Objekte einhüllt.

Sowohl das Basisobjekt als auch die Dekorierer implementieren dieselbe Schnittstelle:

<?php
interface Coffee {
    public function getDescription(): string;
    public function getCost(): float;
}

class SimpleCoffee implements Coffee {
    public function getDescription(): string {
        return "Simple Coffee";
    }
    
    public function getCost(): float {
        return 2.00;
    }
}

Dekorierer hüllen ein anderes Objekt desselben Schnittstellentyps ein:

<?php
class MilkDecorator implements Coffee {
    public function __construct(private Coffee $coffee) {}
    
    public function getDescription(): string {
        return $this->coffee->getDescription() . ", Milk";
    }
    
    public function getCost(): float {
        return $this->coffee->getCost() + 0.50;
    }
}

class SugarDecorator implements Coffee {
    public function __construct(private Coffee $coffee) {}
    
    public function getDescription(): string {
        return $this->coffee->getDescription() . ", Sugar";
    }
    
    public function getCost(): float {
        return $this->coffee->getCost() + 0.25;
    }
}

Verketten Sie Dekorierer, um Verhaltensweisen zu kombinieren:

$coffee = new SimpleCoffee();
$coffee = new MilkDecorator($coffee);
$coffee = new SugarDecorator($coffee);

echo $coffee->getDescription() . " = $" . $coffee->getCost();
// Ausgabe: Simple Coffee, Milk, Sugar = $2.75

Dekorierer können mehrfach und in beliebiger Reihenfolge angewendet werden, ohne dass für jede Kombination eigene Klassen erstellt werden müssen.

Probier es selbst

<?php
require_once 'BasePizza.php';
require_once 'Toppings.php';

// Eingabe lesen - kommagetrennte Liste von Belägen
$input = trim(fgets(STDIN));
$toppings = explode(',', $input);

// Mit einer Basis-MargheritaPizza beginnen
$pizza = new MargheritaPizza();

// TODO: Jeden Belag durchlaufen und die Pizza mit dem entsprechenden Decorator umhüllen
// - "cheese" -> mit CheeseTopping umhüllen
// - "pepperoni" -> mit PepperoniTopping umhüllen
// - "mushroom" -> mit MushroomTopping umhüllen

// TODO: Das Ergebnis im Format ausgeben: [description] = $[price]
// Preis mit number_format() auf 2 Dezimalstellen formatieren

?>
quiz iconTeste dich selbst

Diese Lektion enthält ein kurzes Quiz. Starte die Lektion, um es zu beantworten und deinen Fortschritt zu speichern.

Alle Lektionen in Objektorientierte Programmierung