Menu
Coddy logo textTech

Decorator-Muster

Teil des Abschnitts Objektorientierte Programmierung der C#-Journey von Coddy — Lektion 58 von 70.

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, ergänzen Decorator Funktionalität dynamisch zur Laufzeit, ohne die ursprüngliche Klasse zu verändern.

Das Muster funktioniert so, dass Decorator dieselbe Schnittstelle implementieren wie das Objekt, das sie umschließen. Jeder Decorator hält eine Referenz auf das umschlossene Objekt und delegiert Aufrufe an dieses, wobei er vor oder nach dem Aufruf sein eigenes Verhalten hinzufügt:

public interface ICoffee
{
    string GetDescription();
    decimal GetCost();
}

public class SimpleCoffee : ICoffee
{
    public string GetDescription() => "Coffee";
    public decimal GetCost() => 2.00m;
}

public abstract class CoffeeDecorator : ICoffee
{
    protected ICoffee _coffee;
    
    public CoffeeDecorator(ICoffee coffee) => _coffee = coffee;
    
    public virtual string GetDescription() => _coffee.GetDescription();
    public virtual decimal GetCost() => _coffee.GetCost();
}

public class MilkDecorator : CoffeeDecorator
{
    public MilkDecorator(ICoffee coffee) : base(coffee) { }
    
    public override string GetDescription() => _coffee.GetDescription() + ", Milk";
    public override decimal GetCost() => _coffee.GetCost() + 0.50m;
}

Die Stärke von Dekoratoren liegt in ihrer Stapelbarkeit. Sie können ein dekoriertes Objekt mit einem weiteren Dekorator umhüllen:

ICoffee coffee = new SimpleCoffee();
coffee = new MilkDecorator(coffee);
coffee = new MilkDecorator(coffee);  // Doppelte Milch!

Console.WriteLine(coffee.GetDescription());  // Kaffee, Milch, Milch
Console.WriteLine(coffee.GetCost());         // 3.00

Das Decorator-Pattern ist ideal, wenn Sie Objekten Verantwortlichkeiten hinzufügen müssen, ohne eine Explosion von Unterklassen zu erzeugen. Jeder Decorator konzentriert sich auf ein einzelnes Anliegen, und Sie kombinieren sie nach Bedarf, um genau das gewünschte Verhalten zu erstellen.

challenge icon

Aufgabe

Einfach

Lassen Sie uns ein Pizzabestellsystem mit dem Decorator-Muster erstellen. Sie werden eine Basispizza erstellen und dann dynamisch Beläge hinzufügen, wobei jeder Belag die vorherige Schicht umschließt und sowohl die Beschreibung als auch den Preis ergänzt.

Sie werden Ihren Code in drei Dateien organisieren:

  • Pizza.cs: Definieren Sie ein IPizza-Interface im Namespace PizzaShop mit zwei Methoden: GetDescription(), die einen String zurückgibt, und GetPrice(), die einen Decimal zurückgibt. Erstellen Sie dann eine Klasse PlainPizza, die dieses Interface implementiert – sie sollte "Pizza" als Beschreibung und 8.00m als Basispreis zurückgeben.
  • ToppingDecorators.cs: Erstellen Sie eine abstrakte Klasse ToppingDecorator, die IPizza implementiert und eine Referenz auf die umschlossene Pizza hält (übergeben durch den Konstruktor). Ihre virtuellen Methoden sollten an die umschlossene Pizza delegieren. Erstellen Sie dann drei konkrete Dekoratoren:
    • CheeseTopping – fügt ", Cheese" an die Beschreibung an und addiert 1.50m zum Preis
    • PepperoniTopping – fügt ", Pepperoni" an die Beschreibung an und addiert 2.00m zum Preis
    • MushroomTopping – fügt ", Mushrooms" an die Beschreibung an und addiert 1.25m zum Preis
  • Program.cs: Führen Sie alles zusammen, indem Sie mit einer einfachen Pizza beginnen und diese basierend auf der Eingabe mit Belägen umschließen. Jeder Belag-Dekorator umschließt die vorherige Pizza und baut so die endgültige Bestellung Schicht für Schicht auf.

Sie erhalten eine Eingabe:

  • Eine durch Kommas getrennte Liste von Belägen, die hinzugefügt werden sollen (z. B. cheese,pepperoni,cheese)

Beginnen Sie mit einer PlainPizza und umschließen Sie diese dann mit dem entsprechenden Dekorator für jeden Belag in der Reihenfolge ihres Erscheinens. Gültige Beläge sind cheese, pepperoni und mushrooms. Nachdem Sie alle Beläge angewendet haben, geben Sie die Beschreibung in einer Zeile und den Gesamtpreis in der nächsten Zeile aus.

Wenn die Eingabe beispielsweise cheese,pepperoni,mushrooms lautet, sollte die Ausgabe wie folgt aussehen:

Pizza, Cheese, Pepperoni, Mushrooms
12.75

Beachten Sie, dass Sie denselben Belag mehrmals hinzufügen können! Wenn die Eingabe cheese,cheese lautet, erhalten Sie:

Pizza, Cheese, Cheese
11

Dies demonstriert die Flexibilität des Decorator-Musters – jeder Dekorator umschließt die Pizza, die er erhält, egal ob es sich um eine einfache Pizza oder eine bereits dekorierte handelt, was es Ihnen ermöglicht, Verhaltensweisen dynamisch zu stapeln!

Spickzettel

Das Decorator-Muster ist ein Strukturmuster, das Objekten neue Verhaltensweisen hinzufügt, indem es sie in spezielle Decorator-Objekte einhüllt. Es fügt Funktionalität dynamisch zur Laufzeit hinzu, ohne die ursprüngliche Klasse zu verändern.

Decorators implementieren dieselbe Schnittstelle wie das Objekt, das sie umhüllen, halten eine Referenz auf das umhüllte Objekt und delegieren Aufrufe an dieses, während sie ihr eigenes Verhalten hinzufügen:

public interface ICoffee
{
    string GetDescription();
    decimal GetCost();
}

public class SimpleCoffee : ICoffee
{
    public string GetDescription() => "Coffee";
    public decimal GetCost() => 2.00m;
}

public abstract class CoffeeDecorator : ICoffee
{
    protected ICoffee _coffee;
    
    public CoffeeDecorator(ICoffee coffee) => _coffee = coffee;
    
    public virtual string GetDescription() => _coffee.GetDescription();
    public virtual decimal GetCost() => _coffee.GetCost();
}

public class MilkDecorator : CoffeeDecorator
{
    public MilkDecorator(ICoffee coffee) : base(coffee) { }
    
    public override string GetDescription() => _coffee.GetDescription() + ", Milk";
    public override decimal GetCost() => _coffee.GetCost() + 0.50m;
}

Decorators können gestapelt werden, indem ein dekoriertes Objekt mit einem weiteren Decorator umhüllt wird:

ICoffee coffee = new SimpleCoffee();
coffee = new MilkDecorator(coffee);
coffee = new MilkDecorator(coffee);  // Doppelte Milch!

Console.WriteLine(coffee.GetDescription());  // Coffee, Milk, Milk
Console.WriteLine(coffee.GetCost());         // 3.00

Verwenden Sie das Decorator-Muster, wenn Sie Objekten Verantwortlichkeiten hinzufügen müssen, ohne viele Unterklassen zu erstellen. Jeder Decorator konzentriert sich auf ein einzelnes Anliegen und kann nach Bedarf kombiniert werden.

Probier es selbst

using System;
using PizzaShop;

class Program
{
    public static void Main(string[] args)
    {
        // Lies die kommagetrennte Liste der Beläge ein
        string input = Console.ReadLine();
        string[] toppings = input.Split(',');
        
        // TODO: Beginne mit einer PlainPizza
        
        // TODO: Durchlaufe jeden Belag und umhülle die Pizza
        // mit dem entsprechenden Decorator basierend auf dem Namen des Belags:
        // - "cheese" -> CheeseTopping
        // - "pepperoni" -> PepperoniTopping
        // - "mushrooms" -> MushroomTopping
        
        // TODO: Gib die endgültige Beschreibung und den Preis aus
    }
}
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