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.
Aufgabe
EinfachErstellen 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
Pizzamit den rein virtuellen MethodengetDescription()(gibt eine Zeichenkette zurück) undgetCost()(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 von8.0haben.ToppingDecorator.h: Erstellt die Basisklasse des Decorators und konkrete Belag-Decorators.Erstelle eine Klasse
ToppingDecorator, die vonPizzaerbt und einenstd::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 und1.5zu den Kosten hinzuPepperoniTopping: fügt", Pepperoni"zur Beschreibung und2.0zu den Kosten hinzuMushroomTopping: fügt", Mushrooms"zur Beschreibung und1.0zu 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
yesoderno):- Käse hinzufügen?
- Pepperoni hinzufügen?
- Pilze hinzufügen?
Beginne mit einer
PlainPizzaund 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.5Mit den Eingaben yes, no, yes:
Order: Pizza, Cheese, Mushrooms
Total: $10.5Mit den Eingaben no, no, no:
Order: Pizza
Total: $8Mit den Eingaben yes, yes, yes:
Order: Pizza, Cheese, Pepperoni, Mushrooms
Total: $12.5Beachte, 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;
}
Diese Lektion enthält ein kurzes Quiz. Starte die Lektion, um es zu beantworten und deinen Fortschritt zu speichern.
Alle Lektionen in Objektorientierte Programmierung
1Grundlagen der OOP
Externe DateienC++ Build & KompilierungHeader-Dateien & QuelldateienNamespaces & GültigkeitsbereichEinführung in OOP mit C++Klassen vs. ObjekteDer „this“-ZeigerMethoden (Member-Funktionen)Attribute (Datenmember)Grundlagen zu Ctors & DtorsRückblick – Einfacher Taschenrechner4Klassenattribute
Instanz- vs. statische MemberGetter und SetterConst-MemberfunktionenMutable-SchlüsselwortStatische Methoden und VariablenFriend-Funktionen und -KlassenZusammenfassung – Bankkontoverwaltung7Vererbung
Grundlagen der VererbungZugriffsebenen bei der VererbungAufrufreihenfolge von Ctor & DtorÜberschreiben von MethodenVirtuelle Funktionen & VTableMehrfachvererbungVirtuelle VererbungRückblick – Mitarbeiterhierarchie2Speicherverwaltung
Stack- vs. Heap-SpeicherZeiger und ReferenzenDynamischer Speicher (new/delete)Intelligente Zeiger in C++RAII in C++Rückblick – Dynamischer Array-Manager5Kapselung
Zugriffsbezeichner in C++Zugriffsbezeichner im DetailInformationsverbergungStruct vs. KlasseVerschachtelte und innere KlassenWiederholung – Studierendenverwaltungssystem8Polymorphie
Polymorphie zur Compilezeit vs. LaufzeitFunktionsüberladungVirtuelle Funktionen erneut betrachtetRein virtuelle FunktionenAbstrakte KlassenInterface-Design in C++Dynamic Casting & RTTIRückblick – Shape-Calculator11Fortgeschrittene OOP-Konzepte
Komposition vs. VererbungMixins mittels CRTPPimpl-IdiomTyplöschungEnum-Klassen & starke TypisierungAusnahmebehandlung in OOPBenutzerdefinierte Ausnahmehierarchien14Entwurfsmuster Teil 2
BefehlsmusterAdaptermusterDekorierermusterTemplate-Method-MusterZustandsmusterKompositmusterRAII als Muster3Konstruktoren & Destruktoren
StandardkonstruktorParametrisierter KonstruktorKopierkonstruktorMove-KonstruktorKonstruktor-InitialisierungslistenDelegierende KonstruktorenDestruktor im DetailDrei-/Fünf-/Null-RegelZusammenfassung – String-Klasse6Operatorüberladung
Einführung in die OperatorüberladungArithmetische OperatorüberladungVergleichsoperatorüberladungStream-OperatorenZuweisungsoperatorüberladungÜberladung der Operatoren [] und ()TypkonvertierungsoperatorenWiederholung – Matrix-Klasse9Templates
FunktionstemplatesKlassen-TemplatesTemplate-SpezialisierungVariadische TemplatesGrundlagen zu SFINAE und Type TraitsRückblick – Generischer ContainerÜbe selbstständig: Online-C++-Compiler