Decorator-Muster
Teil des Abschnitts Objektorientierte Programmierung der Java-Journey von Coddy — Lektion 72 von 87.
Das Decorator-Pattern ist ein Strukturmuster, das es Ihnen ermöglicht, Objekten neue Verhaltensweisen hinzuzufügen, indem Sie diese in spezielle Objekte, sogenannte Decorator, einhüllen. Im Gegensatz zur Vererbung, die Verhalten zur Kompilierzeit hinzufügt, fügen Decorator Funktionalität dynamisch zur Laufzeit hinzu, ohne die ursprüngliche Klasse zu verändern.
Stellen Sie sich ein Café vor, in dem Sie mit einem einfachen Kaffee beginnen und Extras wie Milch, Zucker oder Schlagsahne hinzufügen können. Jede Ergänzung umschließt den ursprünglichen Kaffee und fügt dessen Kosten und Beschreibung hinzu. Das Muster verwendet eine gemeinsame Schnittstelle, sodass dekorierte Objekte genauso behandelt werden können wie das Original:
interface Coffee {
String getDescription();
double getCost();
}
class SimpleCoffee implements Coffee {
public String getDescription() {
return "Simple Coffee";
}
public double getCost() {
return 2.0;
}
}
abstract class CoffeeDecorator implements Coffee {
protected Coffee coffee;
public CoffeeDecorator(Coffee coffee) {
this.coffee = coffee;
}
}
class MilkDecorator extends CoffeeDecorator {
public MilkDecorator(Coffee coffee) {
super(coffee);
}
public String getDescription() {
return coffee.getDescription() + ", Milk";
}
public double getCost() {
return coffee.getCost() + 0.5;
}
}Der Decorator hält eine Referenz auf das umhüllte Objekt und delegiert Aufrufe an dieses, während er sein eigenes Verhalten hinzufügt. Sie können mehrere Decorator stapeln, um Funktionen zu kombinieren:
Coffee order = new SimpleCoffee();
order = new MilkDecorator(order);
order = new SugarDecorator(order);
System.out.println(order.getDescription()); // Einfacher Kaffee, Milch, Zucker
System.out.println(order.getCost()); // 2.75Das Decorator-Pattern glänzt, wenn Sie Objekten Verantwortlichkeiten hinzufügen müssen, ohne eine Explosion von Unterklassen zu verursachen. Jeder Decorator ist unabhängig, sodass Sie sie frei kombinieren können, um genau die Kombination von Verhaltensweisen zu erstellen, die Sie benötigen.
Aufgabe
EinfachLassen Sie uns ein Benachrichtigungssystem mit dem Decorator-Pattern erstellen! Sie werden eine Basisbenachrichtigung erstellen, die durch verschiedene Zustellungskanäle – E-Mail-Benachrichtigungen, SMS-Benachrichtigungen und Push-Benachrichtigungen – erweitert werden kann, indem Decorator umeinander gewickelt werden. Dies spiegelt reale Systeme wider, in denen eine einzelne Nachricht möglicherweise gleichzeitig über mehrere Kanäle gesendet werden muss.
Sie werden Ihren Code in vier Dateien organisieren:
Notifier.java: Definieren Sie dasNotifier-Interface, das als Komponenten-Interface für Ihr Decorator-Pattern dient. Es sollte eine Methodesend(String message)deklarieren, die das Senden von Benachrichtigungen übernimmt.BasicNotifier.java: Erstellen Sie die konkrete Komponentenklasse, dieNotifierimplementiert. Dies stellt die einfachste Form der Benachrichtigung dar – einen In-App-Alarm. Wennsendaufgerufen wird, sollte"In-App: " + messageausgegeben werden.NotifierDecorators.java: Erstellen Sie hier Ihre Decorator-Hierarchie. Beginnen Sie mit einer abstrakten KlasseNotifierDecorator, dieNotifierimplementiert und eine Referenz auf ein gewickeltesNotifier-Objekt hält (das über den Konstruktor übergeben wird).Erstellen Sie dann drei konkrete Decorator, die
NotifierDecoratorerweitern:EmailDecorator- Wennsendaufgerufen wird, delegiert er zuerst an den gewickelten Notifier und gibt dann"Email: " + messageaus.SMSDecorator- Wennsendaufgerufen wird, delegiert er zuerst an den gewickelten Notifier und gibt dann"SMS: " + messageaus.PushDecorator- Wennsendaufgerufen wird, delegiert er zuerst an den gewickelten Notifier und gibt dann"Push: " + messageaus.Main.java: Führen Sie Ihr Decorator-System zusammen! Sie erhalten zwei Eingaben: die Benachrichtigungsnachricht (String) und eine kommagetrennte Liste der hinzuzufügenden Kanäle (String), zum Beispiel"email,sms"oder"push,email,sms".Beginnen Sie mit einem
BasicNotifier. Wickeln Sie diesen dann basierend auf der Kanaleingabe mit den entsprechenden Decorators in der Reihenfolge ihres Erscheinens ein. Gültige Kanalnamen sind"email","sms"und"push".Rufen Sie schließlich
sendmit Ihrer Nachricht auf dem vollständig dekorierten Notifier auf.
Sie erhalten zwei Eingaben in dieser Reihenfolge: die zu sendende Nachricht (String) und die zu aktivierenden Kanäle (String, kommagetrennt).
Zum Beispiel bei den Eingaben "Server is down!" und "email,push" wäre Ihre Ausgabe:
In-App: Server is down!
Email: Server is down!
Push: Server is down!Beachten Sie, wie jeder Decorator sein eigenes Verhalten hinzufügt, während die ursprüngliche Benachrichtigung erhalten bleibt. Die Reihenfolge des Wickelns bestimmt die Reihenfolge der Ausgabe – die innerste Komponente (BasicNotifier) wird zuerst ausgeführt, dann fügt jeder Decorator seine Ebene hinzu. Sie können jede Kombination von Kanälen mischen und anpassen, ohne separate Klassen für jede Kombination erstellen zu müssen!
Spickzettel
Das Decorator-Pattern (Dekorierer-Muster) ist ein Strukturmuster, das Objekten neue Verhaltensweisen hinzufügt, indem es sie in Dekorierer-Objekte einhüllt. Es fügt Funktionalität dynamisch zur Laufzeit hinzu, ohne die ursprüngliche Klasse zu verändern.
Das Muster verwendet eine gemeinsame Schnittstelle, sodass dekorierte Objekte genauso behandelt werden können wie das ursprüngliche Objekt.
Grundstruktur
Definieren Sie eine Komponenten-Schnittstelle (Interface):
interface Coffee {
String getDescription();
double getCost();
}Erstellen Sie eine konkrete Komponente, die das Interface implementiert:
class SimpleCoffee implements Coffee {
public String getDescription() {
return "Simple Coffee";
}
public double getCost() {
return 2.0;
}
}Erstellen Sie eine abstrakte Dekorierer-Klasse, die dasselbe Interface implementiert und eine Referenz auf das eingehüllte Objekt hält:
abstract class CoffeeDecorator implements Coffee {
protected Coffee coffee;
public CoffeeDecorator(Coffee coffee) {
this.coffee = coffee;
}
}Erstellen Sie konkrete Dekorierer, die den abstrakten Dekorierer erweitern:
class MilkDecorator extends CoffeeDecorator {
public MilkDecorator(Coffee coffee) {
super(coffee);
}
public String getDescription() {
return coffee.getDescription() + ", Milk";
}
public double getCost() {
return coffee.getCost() + 0.5;
}
}Verwendung von Dekorierern
Stapeln Sie mehrere Dekorierer, um Funktionen zu kombinieren:
Coffee order = new SimpleCoffee();
order = new MilkDecorator(order);
order = new SugarDecorator(order);
System.out.println(order.getDescription()); // Simple Coffee, Milk, Sugar
System.out.println(order.getCost()); // 2.75Der Dekorierer delegiert Aufrufe an das eingehüllte Objekt, während er sein eigenes Verhalten hinzufügt. Jeder Dekorierer ist unabhängig und kann frei kombiniert werden.
Probier es selbst
import java.util.Scanner;
class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
// Lies die zu sendende Nachricht
String message = scanner.nextLine();
// Lies die kommagetrennte Liste der Kanäle (z. B. "email,sms" oder "push,email,sms")
String channelsInput = scanner.nextLine();
// TODO: Beginne mit einem BasicNotifier
// TODO: Teile die Kanaleingabe anhand des Kommas auf
// TODO: Durchlaufe jeden Kanal und umhülle den Notifier mit dem entsprechenden Decorator
// Gültige Kanäle sind: "email", "sms", "push"
// TODO: Rufe send auf dem vollständig dekorierten Notifier mit der Nachricht auf
}
}
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 DateienEinführung in die OOPKlassen vs. ObjekteDas this-SchlüsselwortMethodenFelder (Attribute)Konstruktor-MethodeKonstruktor-ÜberladungRückblick – Einfacher Taschenrechner4Vererbung
Grundlagen der Vererbung (extends)Das super-SchlüsselwortMethoden überschreiben (@Override)Konstruktor-VerkettungDie Object-KlasseEinfache & mehrstufige VererbungWarum keine Mehrfachvererbung von KlassenZusammenfassung - Mitarbeiter-Hierarchie7Spezielle Methoden & Object-Klasse
toString()-Methodeequals() und hashCode()clone()-MethodecompareTo() und ComparableComparator-InterfaceZusammenfassung – Benutzerdefiniertes Sortieren2Zugriffsmodifikatoren & Kapselung
Überblick über ZugriffsebenenGetter- und Setter-MethodenInformation HidingDas final-SchlüsselwortRückblick – Bankkonto-Manager5Polymorphismus
Grundlagen der MethodenüberladungMethodenüberschreibung (Laufzeit)Upcasting und DowncastingDer instanceof-OperatorAbstrakte Klassen und MethodenZusammenfassung – Formenrechner8Fortgeschrittene OOP-Konzepte
Komposition vs. VererbungAggregation vs. KompositionInnere, geschachtelte & anonyme KlassenEnums und Enum-MethodenRecords (Java 16+)Sealed Classes (Java 17+)11Design Patterns Teil 1
Einführung in Design PatternsSingleton-PatternFactory-PatternBuilder-PatternObserver-PatternStrategy-Pattern14Abschlussherausforderungen
E-Learning-PlattformBanksystemSpielcharakter-EntwicklungFahrzeugvermietung3Klasseneigenschaften & Statische Member
Instanz- vs. statische VariablenStatische MethodenStatische BlöckeKonstanten (static final)Zusammenfassung – Zähler & Utility6Interfaces & Abstrakte Klassen
Einführung in InterfacesInterfaces implementierenImplementierung mehrerer InterfacesDefault- & statische Methoden in InterfacesAbstrakte Klassen vs. InterfacesFunktionale InterfacesZusammenfassung - Zahlungssystem9Generics
Einführung in GenericsGenerische KlassenGenerische MethodenEingeschränkte TypparameterWildcards (?, extends, super)Zusammenfassung – Generische Container12Entwurfsmuster Teil 2
Command-MusterAdapter-MusterDecorator-MusterTemplate-Method-MusterState-MusterComposite-MusterIterator-Muster