Menu
Coddy logo textTech

Dekorierermuster

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

Das Dekorator-Muster ermöglicht es dir, Objekten dynamisch neue Verhaltensweisen hinzuzufügen, indem du sie in spezielle Objekte, sogenannte Dekoratoren, einhüllst. Im Gegensatz zur Vererbung, die Verhalten zur Kompilierungszeit hinzufügt, kannst du mit Dekoratoren die Funktionalität zur Laufzeit erweitern, ohne die ursprüngliche Klasse zu ändern.

Das Muster funktioniert, indem sowohl das ursprüngliche Objekt als auch die Dekorierer dieselbe Schnittstelle implementieren. Jeder Dekorierer enthält eine Referenz auf eine Komponente und fügt sein eigenes Verhalten hinzu, bevor er die Ausführung an das umschlossene Objekt delegiert oder danach:

#include <iostream>
#include <memory>

// Komponentenschnittstelle
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 verschachtelt 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
}

Verwende Decorator, wenn du Objekten dynamisch Verantwortlichkeiten hinzufügen musst, ohne andere Objekte zu beeinflussen, oder wenn das Erweitern der Funktionalität durch Unterklassen zu einer übermäßigen Anzahl von Klassen führen würde.

challenge icon

Aufgabe

Einfach

Erstellen wir ein Pizzabestellsystem mithilfe des Decorator-Musters. Du erstellst ein flexibles System, in dem Kunden mit einer Basispizza 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 idealer Anwendungsfall für Decorators: Beläge können in beliebigen Kombinationen gestapelt werden, ohne eine Vielzahl von Unterklassen erstellen zu müssen.

Du organisierst deinen Code über drei Dateien:

  • Pizza.h: Definiert die Komponentenschnittstelle und die Basispizzaklasse.

    Erstelle eine abstrakte Klasse Pizza mit den rein virtuellen Methoden getDescription() (gibt eine Zeichenkette zurück) und getCost() (gibt einen Gleitkommawert zurück) sowie einem virtuellen Destruktor.

    Erstelle anschließend eine konkrete Klasse PlainPizza, die die Basispizza darstellt. Sie soll "Pizza" als Beschreibung zurückgeben und Basiskosten von 8.0 haben.

  • ToppingDecorator.h: Erstellt die Basisklasse des Decorators und konkrete Belag-Decorators.

    Erstelle eine Klasse ToppingDecorator, die von Pizza erbt und einen std::unique_ptr<Pizza> auf die umschlossene Komponente enthält. Sie dient als Grundlage für alle Belag-Decorators.

    Implementiere drei konkrete Belag-Decorators:

    • 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 soll an die umschlossene Pizza delegieren und anschließend seinen eigenen Beitrag hinzufügen.

  • main.cpp: Erstellt eine individuelle Pizzabestellung auf Grundlage der Benutzereingaben.

    Lies drei Eingaben ein (jede lautet yes oder no):

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

    Beginne mit einer PlainPizza und umschließe sie anschließend abhängig von den Eingaben mit den passenden Belag-Decorators (in der Reihenfolge: Käse, Pepperoni, Pilze). Gib schließlich die Bestelldetails in diesem Format aus:

    Order: [description]
    Total: $[cost]

Beispiel mit den Eingaben yes, yes, no:

Order: Pizza, Cheese, Pepperoni
Total: $11.5

Mit den Eingaben yes, no, yes:

Order: Pizza, Cheese, Mushrooms
Total: $10.5

Mit den Eingaben no, no, no:

Order: Pizza
Total: $8

Mit den Eingaben yes, yes, yes:

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

Beachte, wie jeder Decorator den vorherigen umschließt und dabei Beschreibung und Kosten Schicht für Schicht erweitert. Du kannst problemlos neue Beläge hinzufügen, indem du neue Decorator-Klassen erstellst, ohne den bestehenden Pizza- oder Belag-Code zu ändern: Das ist die Stärke des Decorator-Musters.

Probier es selbst

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

using namespace std;

int main() {
    // Lies drei Eingaben (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, umhülle die Pizza mit CheeseTopping
    
    // TODO: Wenn addPepperoni "yes" ist, umhülle die Pizza mit PepperoniTopping
    
    // TODO: Wenn addMushrooms "yes" ist, umhülle die Pizza mit MushroomTopping
    
    // TODO: Gib die Bestellung im folgenden Format aus:
    // Bestellung: [description]
    // Gesamt: $[cost]
    
    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

Übe selbstständig: Online-C++-Compiler