Menu
Coddy logo textTech

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.75

Das 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.

challenge icon

Aufgabe

Einfach

Lassen 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 das Notifier-Interface, das als Komponenten-Interface für Ihr Decorator-Pattern dient. Es sollte eine Methode send(String message) deklarieren, die das Senden von Benachrichtigungen übernimmt.
  • BasicNotifier.java: Erstellen Sie die konkrete Komponentenklasse, die Notifier implementiert. Dies stellt die einfachste Form der Benachrichtigung dar – einen In-App-Alarm. Wenn send aufgerufen wird, sollte "In-App: " + message ausgegeben werden.
  • NotifierDecorators.java: Erstellen Sie hier Ihre Decorator-Hierarchie. Beginnen Sie mit einer abstrakten Klasse NotifierDecorator, die Notifier implementiert und eine Referenz auf ein gewickeltes Notifier-Objekt hält (das über den Konstruktor übergeben wird).

    Erstellen Sie dann drei konkrete Decorator, die NotifierDecorator erweitern:

    EmailDecorator - Wenn send aufgerufen wird, delegiert er zuerst an den gewickelten Notifier und gibt dann "Email: " + message aus.

    SMSDecorator - Wenn send aufgerufen wird, delegiert er zuerst an den gewickelten Notifier und gibt dann "SMS: " + message aus.

    PushDecorator - Wenn send aufgerufen wird, delegiert er zuerst an den gewickelten Notifier und gibt dann "Push: " + message aus.

  • 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 send mit 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.75

Der 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
        
    }
}
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