Dekorierermuster
Teil des Abschnitts Objektorientierte Programmierung der C#-Journey von Coddy. Lektion 58 von 70.
Das Dekorator-Muster ist ein strukturelles Muster, 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 Kompilierzeit hinzufügt, erweitern Dekoratoren die Funktionalität dynamisch zur Laufzeit, ohne die ursprüngliche Klasse zu verändern.
Das Muster funktioniert, indem Dekorierer dieselbe Schnittstelle wie das Objekt implementieren, das sie umschließen. Jeder Dekorierer enthält eine Referenz auf das umschlossene Objekt und delegiert Aufrufe an dieses, wobei er davor oder danach 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 Fähigkeit, gestapelt zu werden. 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-Muster ist ideal, wenn du Objekten Verantwortlichkeiten hinzufügen musst, ohne eine Flut von Unterklassen zu erzeugen. Jeder Decorator konzentriert sich auf einen einzelnen Aspekt, und du kombinierst sie nach Bedarf, um genau das gewünschte Verhalten zu erstellen.
Aufgabe
EinfachErstellen wir ein Pizza-Bestellsystem mithilfe des Decorator-Musters. Du erstellst eine Basispizza und fügst ihr dann dynamisch Beläge hinzu, wobei jeder Belag die vorherige Schicht umschließt und sowohl die Beschreibung als auch den Preis erweitert.
Du organisierst deinen Code auf drei Dateien verteilt:
Pizza.cs: Definiere eineIPizza-Schnittstelle im NamespacePizzaShopmit zwei Methoden:GetDescription(), die eine Zeichenkette zurückgibt, undGetPrice(), die eine Dezimalzahl zurückgibt. Erstelle anschließend einePlainPizza-Klasse, die diese Schnittstelle implementiert – sie soll"Pizza"als Beschreibung und8.00mals Grundpreis zurückgeben.ToppingDecorators.cs: Erstelle eine abstrakteToppingDecorator-Klasse, dieIPizzaimplementiert und eine Referenz auf die umschlossene Pizza enthält (über den Konstruktor übergeben). Ihre virtuellen Methoden sollen die Aufrufe an die umschlossene Pizza weiterleiten. Erstelle anschließend drei konkrete Decorators:CheeseTopping– hängt", Cheese"an die Beschreibung an und addiert1.50mzum PreisPepperoniTopping– hängt", Pepperoni"an die Beschreibung an und addiert2.00mzum PreisMushroomTopping– hängt", Mushrooms"an die Beschreibung an und addiert1.25mzum Preis
Program.cs: Führe alles zusammen, indem du mit einer einfachen Pizza beginnst und sie abhängig von der Eingabe mit Belägen umschließt. Jeder Belag-Decorator umschließt die vorherige Pizza, sodass die endgültige Bestellung Schicht für Schicht aufgebaut wird.
Du erhältst eine Eingabe:
- Eine durch Kommas getrennte Liste der hinzuzufügenden Beläge (z. B.
cheese,pepperoni,cheese)
Beginne mit einer PlainPizza und umschließe sie anschließend für jeden Belag in der Reihenfolge, in der die Beläge erscheinen, mit dem passenden Decorator. Gültige Beläge sind cheese, pepperoni und mushrooms. Gib nach dem Anwenden aller Beläge 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 so aussehen:
Pizza, Cheese, Pepperoni, Mushrooms
12.75Beachte, dass du denselben Belag mehrmals hinzufügen kannst! Wenn die Eingabe cheese,cheese lautet, erhältst du:
Pizza, Cheese, Cheese
11.00Dies veranschaulicht die Flexibilität des Decorator-Musters: Jeder Decorator umschließt die Pizza, die er erhält – unabhängig davon, ob es sich um eine einfache Pizza oder eine bereits dekorierte Pizza handelt. Dadurch kannst du Verhaltensweisen dynamisch stapeln!
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 passenden 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. EigenschaftenKonstruktorenObjektinitialisiererWiederholung – Einfacher Taschenrechner4Vererbung
Grundlegende Vererbungs-(:)-SyntaxDas „base“-Schlüsselwort„virtual“- und „override“-SchlüsselwörterVersiegelte KlassenDie „object“-BasisklasseWiederholung – Mitarbeiterhierarchie7Erweiterte Funktionen
Überladen von OperatorenIndexer (this[])ToString()-ÜberschreibungErweiterungsmethodenRückblick – Benutzerdefinierte Liste10Entwurfsmuster Teil 1
Einführung in EntwurfsmusterThread-sicheres SingletonFactory-MusterObserver-Muster (Events)Strategy-Muster13Abschließende Herausforderungen
E-Learning-PlattformKomponente für SpielfigurenFahrzeugvermietungsdienst2Eigenschaften & statische Member
Automatisch implementierte EigenschaftenNur lesbare/schreibbare EigenschaftenStatische Felder & MethodenStatische KlassenMember mit Ausdruckskörpern5Polymorphie & Interfaces
Compile- vs. Laufzeit-PolymorphieInterface vs. abstrakte KlasseMehrere InterfacesExplizite InterfacesUpcasting & DowncastingWiederholung – Formenrechner8Fortgeschrittene OOP-Konzepte
Komposition statt VererbungGenerics (Klassen & Methoden)Delegates und EventsAttribute und ReflectionIDisposable & using-AnweisungGrundlagen der Dependency Injection11Entwurfsmuster Teil 2
BefehlsmusterAdaptermusterDekorierermusterVorlagenmethodenmusterZustandsmusterKompositumsmuster3Klassenarchitektur
Instanz- vs. statische DatenSchlüsselwörter 'readonly' & 'const'Backing FieldsRückblick – Bankkontenverwaltung6Kapselung
ZugriffsmodifikatorenEigenschaften zur KapselungImplementierung der DatenkapselungMuster für UnveränderlichkeitRückblick – SchülerdatenÜbe selbstständig: Online-C#-Compiler