Menu
Coddy logo textTech

Decorator-Muster

Teil des Abschnitts Objektorientierte Programmierung der C++-Journey von Coddy — Lektion 98 von 104.

Das Decorator-Pattern ermöglicht es Ihnen, Objekten dynamisch neue Verhaltensweisen hinzuzufügen, indem Sie diese in spezielle Objekte, sogenannte Decorators, einhüllen. Im Gegensatz zur Vererbung, die Verhalten zur Kompilierzeit hinzufügt, ermöglichen es Decorators, die Funktionalität zur Laufzeit zu erweitern, ohne die ursprüngliche Klasse zu ändern.

Das Muster funktioniert, indem sowohl das ursprüngliche Objekt als auch die Dekoratoren dieselbe Schnittstelle implementieren. Jeder Dekorator hält eine Referenz auf eine Komponente und fügt sein eigenes Verhalten hinzu, bevor oder nachdem er an das umhüllte Objekt delegiert:

#include <iostream>
#include <memory>

// Komponenten-Schnittstelle
class Coffee {
public:
    virtual std::string getDescription() const = 0;
    virtual double getCost() const = 0;
    virtual ~Coffee() = default;
};

// Konkrete Komponente
class SimpleCoffee : public Coffee {
public:
    std::string getDescription() const override { return "Coffee"; }
    double getCost() const override { return 2.0; }
};

// Basis-Dekorierer
class CoffeeDecorator : public Coffee {
protected:
    std::unique_ptr<Coffee> coffee;
public:
    CoffeeDecorator(std::unique_ptr<Coffee> c) : coffee(std::move(c)) {}
};

// Konkrete Dekorierer
class MilkDecorator : public CoffeeDecorator {
public:
    MilkDecorator(std::unique_ptr<Coffee> c) : CoffeeDecorator(std::move(c)) {}
    std::string getDescription() const override {
        return coffee->getDescription() + ", Milk";
    }
    double getCost() const override { return coffee->getCost() + 0.5; }
};

class SugarDecorator : public CoffeeDecorator {
public:
    SugarDecorator(std::unique_ptr<Coffee> c) : CoffeeDecorator(std::move(c)) {}
    std::string getDescription() const override {
        return coffee->getDescription() + ", Sugar";
    }
    double getCost() const override { return coffee->getCost() + 0.2; }
};

Dekoratoren können gestapelt werden – jeder umschließt den vorherigen und baut die Funktionalität Schicht für Schicht auf:

int main() {
    std::unique_ptr<Coffee> order = std::make_unique<SimpleCoffee>();
    order = std::make_unique<MilkDecorator>(std::move(order));
    order = std::make_unique<SugarDecorator>(std::move(order));
    
    std::cout << order->getDescription() << ": $" << order->getCost();
    // Ausgabe: Coffee, Milk, Sugar: $2.7
}

Verwenden Sie den Decorator, wenn Sie Objekten dynamisch Zuständigkeiten hinzufügen müssen, ohne andere Objekte zu beeinflussen, oder wenn die Erweiterung der Funktionalität durch Unterklassenbildung zu einer Explosion von Klassen führen würde.

challenge icon

Aufgabe

Einfach

Lassen Sie uns ein Pizza-Bestellsystem mit dem Decorator-Pattern erstellen. Sie werden ein flexibles System entwickeln, bei dem Kunden mit einer Basis-Pizza beginnen und verschiedene Beläge hinzufügen können – jeder Belag umschließt die vorherige Bestellung und fügt seine eigenen Kosten und seine eigene Beschreibung hinzu. Dies ist ein perfekter Anwendungsfall für Decorator: Beläge können in jeder beliebigen Kombination gestapelt werden, ohne dass eine Explosion von Unterklassen entsteht.

Sie werden Ihren Code auf drei Dateien verteilen:

  • Pizza.h: Definieren Sie das Komponenten-Interface und die Basis-Pizza-Klasse.

    Erstellen Sie eine abstrakte Klasse Pizza mit den rein virtuellen Methoden getDescription() (gibt einen String zurück) und getCost() (gibt einen Double zurück), sowie einem virtuellen Destruktor.

    Erstellen Sie dann eine konkrete Klasse PlainPizza, die die Basis-Pizza darstellt. Sie sollte "Pizza" als Beschreibung zurückgeben und einen Basispreis von 8.0 haben.

  • ToppingDecorator.h: Erstellen Sie die Decorator-Basisklasse und konkrete Belag-Decorator.

    Erstellen Sie eine Klasse ToppingDecorator, die von Pizza erbt und einen std::unique_ptr<Pizza> auf die umschlossene Komponente hält. Dies dient als Basis für alle Belag-Decorator.

    Implementieren Sie drei konkrete Belag-Decorator:

    • CheeseTopping — fügt ", Cheese" zur Beschreibung und 1.5 zu den Kosten hinzu
    • PepperoniTopping — fügt ", Pepperoni" zur Beschreibung und 2.0 zu den Kosten hinzu
    • MushroomTopping — fügt ", Mushrooms" zur Beschreibung und 1.0 zu den Kosten hinzu

    Jeder Decorator sollte an die umschlossene Pizza delegieren und dann seinen eigenen Beitrag hinzufügen.

  • main.cpp: Erstellen Sie eine individuelle Pizza-Bestellung basierend auf Benutzereingaben.

    Lesen Sie drei Eingaben ein (jeweils yes oder no):

    1. Käse hinzufügen?
    2. Salami hinzufügen?
    3. Pilze hinzufügen?

    Beginnen Sie mit einer PlainPizza und umschließen Sie diese dann mit den entsprechenden Belag-Decoratoren basierend auf den Eingaben (in der Reihenfolge: Käse, Salami, Pilze). Geben Sie abschließend die Bestelldetails in diesem Format aus:

    Order: [description]
    Total: $[cost]

Zum Beispiel bei den Eingaben yes, yes, no:

Order: Pizza, Cheese, Pepperoni
Total: $11.5

Bei den Eingaben yes, no, yes:

Order: Pizza, Cheese, Mushrooms
Total: $10.5

Bei den Eingaben no, no, no:

Order: Pizza
Total: $8

Bei den Eingaben yes, yes, yes:

Order: Pizza, Cheese, Pepperoni, Mushrooms
Total: $12.5

Beachten Sie, wie jeder Decorator den vorherigen umschließt und so Schicht für Schicht sowohl die Beschreibung als auch die Kosten aufbaut. Sie können problemlos neue Beläge hinzufügen, indem Sie neue Decorator-Klassen erstellen, ohne den bestehenden Pizza- oder Belag-Code zu ändern – das ist das Schöne am Decorator-Pattern.

Probier es selbst

#include <iostream>
#include <string>
#include <memory>
#include "Pizza.h"
#include "ToppingDecorator.h"

using namespace std;

int main() {
    // Drei Eingaben lesen (yes oder no)
    string addCheese, addPepperoni, addMushrooms;
    cin >> addCheese;
    cin >> addPepperoni;
    cin >> addMushrooms;
    
    // TODO: Beginne mit einer PlainPizza unter Verwendung von std::unique_ptr
    
    // TODO: Wenn addCheese "yes" ist, die Pizza mit CheeseTopping erweitern
    
    // TODO: Wenn addPepperoni "yes" ist, die Pizza mit PepperoniTopping erweitern
    
    // TODO: Wenn addMushrooms "yes" ist, die Pizza mit MushroomTopping erweitern
    
    // TODO: Die Bestellung in folgendem Format ausgeben:
    // Bestellung: [Beschreibung]
    // Gesamt: $[Kosten]
    
    return 0;
}
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