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.00Das 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.
Aufgabe
EinfachLassen 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 einIPizza-Interface im NamespacePizzaShopmit zwei Methoden:GetDescription(), die einen String zurückgibt, undGetPrice(), die einen Decimal zurückgibt. Erstellen Sie dann eine KlassePlainPizza, die dieses Interface implementiert – sie sollte"Pizza"als Beschreibung und8.00mals Basispreis zurückgeben.ToppingDecorators.cs: Erstellen Sie eine abstrakte KlasseToppingDecorator, dieIPizzaimplementiert 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 addiert1.50mzum PreisPepperoniTopping– fügt", Pepperoni"an die Beschreibung an und addiert2.00mzum PreisMushroomTopping– fügt", Mushrooms"an die Beschreibung an und addiert1.25mzum 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.75Beachten Sie, dass Sie denselben Belag mehrmals hinzufügen können! Wenn die Eingabe cheese,cheese lautet, erhalten Sie:
Pizza, Cheese, Cheese
11Dies 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.00Verwenden 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
}
}
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 DateienNamespaces & DirektivenEinführung in Klassen & ObjekteDas 'this'-SchlüsselwortMethoden und ParameterFelder vs. EigenschaftenKonstruktorenObjekt-InitialisiererZusammenfassung - Einfacher Taschenrechner4Vererbung
Grundlagen der Vererbung (:) SyntaxDas 'base'-SchlüsselwortVirtual & Override SchlüsselwörterVersiegelte KlassenDie 'object'-BasisklasseZusammenfassung – Mitarbeiter-Hierarchie7Fortgeschrittene Funktionen
OperatorüberladungIndexer (this[])ToString() überschreibenErweiterungsmethodenZusammenfassung - Benutzerdefinierte Liste2Eigenschaften & Statische Member
Automatisch implementierte EigenschaftenRead-Only- & Write-Only-EigenschaftenStatische Felder & MethodenStatische KlassenExpression-Bodied Member5Polymorphismus & Interfaces
Compile- vs. Runtime-PolymorphismusInterface vs. abstrakte KlasseMehrfache InterfacesExplizite InterfacesUpcasting & DowncastingRecap – Shape Calculator8Fortgeschrittene OOP-Konzepte
Komposition statt VererbungGenerics (Klassen & Methoden)Delegates und EventsAttribute und ReflectionIDisposable & using-AnweisungDependency Injection Grundlagen11Entwurfsmuster Teil 2
Command-MusterAdapter-MusterDecorator-MusterTemplate-Method-MusterZustandsmusterKompositum-Muster3Klassenarchitektur
Instanz- vs. statische Daten'readonly' & 'const' SchlüsselwörterBacking FieldsZusammenfassung - Bankkonto-Manager6Kapselung
ZugriffsmodifikatorenProperties zur KapselungImplementierung von Data HidingImmutability-MusterRückblick – Studierendendaten