Dekorierermuster
Teil des Abschnitts Objektorientierte Programmierung der Java-Journey von Coddy. Lektion 72 von 87.
Das Dekorator-Muster ist ein strukturelles Entwurfsmuster, mit dem du Objekten neue Verhaltensweisen hinzufügen kannst, indem du sie in spezielle Objekte einhüllst, die als Dekoratoren bezeichnet werden. Im Gegensatz zur Vererbung, die Verhalten zur Kompilierungszeit hinzufügt, erweitern Dekoratoren die Funktionalität dynamisch zur Laufzeit, ohne die ursprüngliche Klasse zu verändern.
Stell dir ein Café vor, in dem du mit einem einfachen Kaffee beginnst und Extras wie Milch, Zucker oder Schlagsahne hinzufügen kannst. Jede Ergänzung umhüllt den ursprünglichen Kaffee und fügt seine Kosten und Beschreibung hinzu. Das Muster verwendet eine gemeinsame Schnittstelle, sodass dekorierte Objekte genauso wie das Original behandelt werden können:
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 enthält eine Referenz auf das umschlossene Objekt und delegiert Aufrufe an dieses, während er sein eigenes Verhalten hinzufügt. Du kannst mehrere Decorators stapeln, 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.75Das Dekorierer-Muster kommt besonders dann zur Geltung, wenn du Objekten Verantwortlichkeiten hinzufügen musst, ohne eine Flut von Unterklassen zu erzeugen. Jeder Dekorierer ist unabhängig, sodass du sie frei kombinieren kannst, um genau die Kombination von Verhaltensweisen zu erstellen, die du benötigst.
Aufgabe
EinfachLass uns ein Benachrichtigungssystem mit dem Decorator Pattern erstellen! Du erstellst eine grundlegende Benachrichtigung, die mit verschiedenen Zustellkanälen, E-Mail-Benachrichtigungen, SMS-Benachrichtigungen und Push-Benachrichtigungen erweitert werden kann, indem Decorator-Objekte ineinander verschachtelt werden. Dies bildet reale Systeme nach, in denen eine einzelne Nachricht möglicherweise gleichzeitig über mehrere Kanäle versendet werden muss.
Du wirst deinen Code auf vier Dateien verteilen:
Notifier.java: Define dieNotifier-Schnittstelle, die als component-Schnittstelle für dein Decorator Pattern dient. Sie sollte eine Methodesend(String message)Declare, die das Senden von Benachrichtigungen übernimmt.BasicNotifier.java: Create die konkrete component-Klasse, dieNotifierimplements. Dies stellt die einfachste Form einer Benachrichtigung dar: eine In-App-Benachrichtigung. Wennsendaufgerufen wird, sollteIn-App: [message]ausgegeben werden.NotifierDecorators.java: Erstelle hier deine Decorator hierarchy. Beginne mit einer abstract-KlasseNotifierDecorator, dieNotifierimplements und eine Referenz auf ein umschlossenesNotifier-Objekt enthält, das über ihren constructor übergeben wird.Erstelle dann drei konkrete Decorator-Klassen, die
NotifierDecoratorerweitern:EmailDecorator– Wennsendaufgerufen wird, delegiert es zunächst an den umschlossenen Notifier und gibt anschließendEmail: [message]aus.SMSDecorator– Wennsendaufgerufen wird, delegiert es zunächst an den umschlossenen Notifier und gibt anschließendSMS: [message]aus.PushDecorator– Wennsendaufgerufen wird, delegiert es zunächst an den umschlossenen Notifier und gibt anschließendPush: [message]aus.Main.java: Führe dein Decorator-System zusammen! Du erhältst zwei Eingaben: die Benachrichtigungsnachricht (String) und eine durch Kommas getrennte Liste der hinzuzufügenden Kanäle (String), zum Beispiel"email,sms"oder"push,email,sms".Beginne mit einem
BasicNotifier. Umschließe ihn dann basierend auf der Kanaleingabe mit den appropriate Decorators in der Reihenfolge, in der sie erscheinen. Gültige Kanalnamen sindemail,smsundpush.Rufe schließlich
sendmit deiner Nachricht auf dem fully dekorierten Notifier auf.
Du erhältst zwei Eingaben in dieser Reihenfolge: die zu sendende Nachricht (String) und die zu aktivierenden Kanäle (String, durch Kommas getrennt).
Bei den Eingaben Server is down! und email,push würde deine Ausgabe beispielsweise folgendermaßen aussehen:
In-App: Server is down!
Email: Server is down!
Push: Server is down!Beachte, wie jeder Decorator sein eigenes Verhalten hinzufügt und dabei die ursprüngliche Benachrichtigung bewahrt. Die Reihenfolge des Umschließens bestimmt die Reihenfolge der Ausgabe: Das innerste component (BasicNotifier) wird zuerst ausgeführt, danach fügt jeder Decorator seine Ebene hinzu. Du kannst beliebige Kombinationen von Kanälen kombinieren, ohne separate Klassen für jede Kombination erstellen zu müssen!
Probier es selbst
import java.util.Scanner;
class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
// Lese die zu sendende Nachricht
String message = scanner.nextLine();
// Lese 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 am Komma auf
// TODO: Gehe jeden Kanal durch 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 Schlüsselwort thisMethodenFelder (Attribute)KonstruktormethodeÜberladen von KonstruktorenRückblick – Einfacher Taschenrechner4Vererbung
Grundlegende Vererbung (extends)Das Schlüsselwort superÜberschreiben von Methoden (@Override)KonstruktorverkettungDie Object-KlasseEinfach- und mehrstufige VererbungWarum keine Mehrfachvererbung von KlassenRückblick – Mitarbeiterhierarchie7Spezielle Methoden & Object-Klasse
toString()-Methodeequals() und hashCode()clone()-MethodecompareTo() und ComparableComparator-SchnittstelleRückblick – Benutzerdefinierte Sortierung2Zugriffsmodifikatoren & Kapselung
Übersicht der ZugriffsebenenGetter- und Setter-MethodenInformationsverbergungDas Schlüsselwort finalRückblick – Bankkontoverwaltung5Polymorphismus
Grundlagen der MethodenüberladungMethodenüberschreibung (Laufzeit)Upcasting und DowncastingDer instanceof-OperatorAbstrakte Klassen und MethodenRückblick – Formrechner8Fortgeschrittene OOP-Konzepte
Komposition vs. VererbungAggregation vs. KompositionInnere, verschachtelte & anonyme KlassenEnums und Enum-MethodenRecords (Java 16+)Versiegelte Klassen (Java 17+)11Entwurfsmuster Teil 1
Einführung in EntwurfsmusterSingleton-MusterFactory-MusterBuilder-MusterObserver-MusterStrategie-Muster14Abschließende Herausforderungen
E-Learning-PlattformBankingsystemEntwicklung einer SpielfigurFahrzeugvermietungsdienst3Klassen-Props & statische Member
Instanz- vs. statische VariablenStatische MethodenStatische BlöckeKonstanten (static final)Wiederholung – Counter & Utility6Schnittstellen & abstrakte Klassen
Einführung in InterfacesInterfaces implementierenMehrere Interfaces implementierenDefault & Static in InterfacesAbstrakte Klassen vs. InterfacesFunktionale InterfacesRückblick – Zahlungssystem9Generics
Einführung in GenericsGenerische KlassenGenerische MethodenGebundene TypparameterWildcards (?, extends, super)Rückblick – Generischer Container12Entwurfsmuster Teil 2
BefehlsmusterAdaptermusterDekorierermusterSchablonenmethodenmusterZustandsmusterKompositmusterIteratormusterÜbe selbstständig: Online-Java-Compiler