Das Decorator Pattern ist ein Strukturmuster, das es ermöglicht, einem Objekt zur Laufzeit zusätzliche Funktionalitäten hinzuzufügen. Es stellt sicher, dass die ursprüngliche Klasse nicht verändert wird. Stattdessen wird die Funktionalität durch die Verwendung von Dekoratoren erweitert. Dies ist besonders nützlich, wenn eine Vielzahl von optionalen Features benötigt wird, die je nach Bedarf hinzugefügt werden können.
Was ist das Decorator Pattern?
Das Decorator Pattern bietet eine flexible Möglichkeit, Objekte zu erweitern, ohne sie direkt zu ändern. Stattdessen wird ein Wrapper um das Objekt gelegt, der die zusätzlichen Funktionalitäten hinzufügt. Dieser Wrapper, der als „Decorator“ bezeichnet wird, folgt der gleichen Schnittstelle wie das ursprüngliche Objekt. Dadurch können Dekoratoren miteinander kombiniert werden, um ein objektorientiertes Design zu ermöglichen.
Es gibt vier Hauptkomponenten im Decorator Pattern:
- Component: Die gemeinsame Schnittstelle, die das dekorierte Objekt und die Dekoratoren implementieren.
- ConcreteComponent: Das konkrete Objekt, das dekoriert werden soll. Es implementiert die
Component
-Schnittstelle. - Decorator: Ein Wrapper, der ebenfalls die
Component
-Schnittstelle implementiert. Der Dekorator hält eine Referenz auf einComponent
-Objekt und delegiert Aufrufe an dieses. - ConcreteDecorator: Eine konkrete Implementierung des Dekorators, der zusätzliche Funktionalität hinzufügt.
Beispiel des Decorator Patterns in C++
Angenommen, wir entwickeln ein System für das Erstellen von Textnachrichten. Wir wollen verschiedene Features wie „Fett“, „Kursiv“ und „Unterstrichen“ hinzufügen. Anstatt eine Vielzahl von Klassen zu erstellen, um jede Kombination dieser Eigenschaften abzubilden, können wir das Decorator Pattern verwenden.
#include <iostream>
#include <memory>
#include <string>
// Component: Gemeinsame Schnittstelle
class Message {
public:
virtual void send() const = 0;
virtual ~Message() = default;
};
// ConcreteComponent: Das ursprüngliche Objekt
class SimpleMessage : public Message {
private:
std::string text;
public:
SimpleMessage(const std::string& text) : text(text) {}
void send() const override {
std::cout << text << std::endl;
}
};
// Decorator: Der abstrakte Dekorator, der die Component-Schnittstelle implementiert
class MessageDecorator : public Message {
protected:
std::shared_ptr<Message> wrappedMessage;
public:
MessageDecorator(std::shared_ptr<Message> message) : wrappedMessage(message) {}
void send() const override {
wrappedMessage->send();
}
};
// ConcreteDecorator: Ein konkreter Dekorator, der zusätzliche Funktionalität hinzufügt
class BoldDecorator : public MessageDecorator {
public:
BoldDecorator(std::shared_ptr<Message> message) : MessageDecorator(message) {}
void send() const override {
std::cout << "<b>";
MessageDecorator::send();
std::cout << "</b>";
}
};
class ItalicDecorator : public MessageDecorator {
public:
ItalicDecorator(std::shared_ptr<Message> message) : MessageDecorator(message) {}
void send() const override {
std::cout << "<i>";
MessageDecorator::send();
std::cout << "</i>";
}
};
// Client-Code
int main() {
std::shared_ptr<Message> message = std::make_shared<SimpleMessage>("Hallo, Welt!");
// Dekorieren mit Fettdruck
std::shared_ptr<Message> boldMessage = std::make_shared<BoldDecorator>(message);
boldMessage->send(); // <b>Hallo, Welt!</b>
// Dekorieren mit Kursiv
std::shared_ptr<Message> italicMessage = std::make_shared<ItalicDecorator>(boldMessage);
italicMessage->send(); // <i><b>Hallo, Welt!</b></i>
return 0;
}
Erklärung des C++-Beispiels
- Component: Die
Message
-Klasse definiert die gemeinsame Schnittstelle mit der Methodesend()
, die die Nachricht sendet. Sowohl die ursprüngliche Nachricht als auch die Dekoratoren müssen diese Methode implementieren. - ConcreteComponent: Die Klasse
SimpleMessage
ist das grundlegende, nicht dekorierte Objekt. Sie speichert den Text und implementiert diesend()
-Methode. - Decorator: Die
MessageDecorator
-Klasse ist der abstrakte Dekorator. Sie nimmt einMessage
-Objekt als Parameter und ruft dessensend()
-Methode auf. Diese Klasse ermöglicht das Hinzufügen von Funktionalität durch Vererbung. - ConcreteDecorator: Die
BoldDecorator
undItalicDecorator
erweitern den Dekorator, um zusätzliche Funktionalität hinzuzufügen.BoldDecorator
fügt<b>
-Tags hinzu, währendItalicDecorator
<i>
-Tags hinzufügt.
Im Client-Code wird zuerst eine einfache Nachricht erstellt. Anschließend wird diese Nachricht mit verschiedenen Dekoratoren versehen. Jede zusätzliche Dekoration wird durch Erstellen eines neuen Dekorators erreicht, der das bestehende Message
-Objekt erweitert.
Vorteile des Decorator Patterns
- Flexibilität: Das Decorator Pattern erlaubt es, einem Objekt dynamisch zusätzliche Funktionalitäten hinzuzufügen. Dekoratoren können auch miteinander kombiniert werden, um unterschiedliche Effekte zu erzielen.
- Vermeidung von Subklassierung: Statt viele Subklassen zu erstellen, die verschiedene Kombinationen von Eigenschaften abbilden, können Dekoratoren verwendet werden. Das reduziert die Anzahl der Klassen und erhöht die Wartbarkeit.
- Unabhängigkeit der Dekorationen: Jeder Dekorator ist unabhängig von anderen Dekoratoren. Das bedeutet, dass neue Dekoratoren problemlos hinzugefügt werden können, ohne den bestehenden Code zu verändern.
- Erweiterbarkeit: Wenn neue Funktionalitäten benötigt werden, können neue Dekoratoren hinzugefügt werden, ohne dass Änderungen an der ursprünglichen Klasse vorgenommen werden müssen.
Nachteile des Decorator Patterns
- Komplexität: Wenn zu viele Dekoratoren verwendet werden, kann das System sehr komplex und schwer verständlich werden. Zu viele dekorierte Objekte können die Lesbarkeit des Codes beeinträchtigen.
- Kombination von Dekoratoren: Bei der Kombination mehrerer Dekoratoren muss darauf geachtet werden, dass sie miteinander kompatibel sind. Andernfalls können unvorhersehbare Ergebnisse auftreten.
- Zusätzlicher Speicherverbrauch: Jeder Dekorator führt zu einem zusätzlichen Wrapper-Objekt. Dies kann zu einem höheren Speicherverbrauch führen, wenn viele Dekoratoren verwendet werden.
Fazit
Das Decorator Pattern ist ein nützliches Muster, das es ermöglicht, die Funktionalität eines Objekts dynamisch zu erweitern. Durch die Verwendung von Dekoratoren können zusätzliche Features hinzugefügt werden, ohne dass die ursprüngliche Klasse verändert werden muss. In C++ wird das Muster durch die Verwendung von abstrakten Dekoratoren und konkreten Implementierungen leicht verständlich.
Obwohl das Muster zu zusätzlicher Komplexität führen kann, insbesondere bei der Verwendung vieler Dekoratoren, bietet es eine hohe Flexibilität und Erweiterbarkeit. Das Decorator Pattern ist besonders vorteilhaft, wenn es darum geht, Objekten nachträglich neue Funktionalitäten hinzuzufügen, ohne den Code stark zu verändern.
Zurück zur Liste der Pattern: Liste der Design-Pattern