Menu
Coddy logo textTech

Decorator-Muster

Teil des Abschnitts Object Oriented Programming der Python-Journey von Coddy — Lektion 51 von 64.

Das Decorator-Pattern fügt Objekten dynamisch neue Funktionalität hinzu, ohne deren Struktur zu ändern. Es kapselt Objekte ein, um deren Verhalten zu erweitern.

Hier ist ein einfaches Kaffee-Beispiel:

class Coffee:
    def cost(self):
        return 5
    
    def description(self):
        return "Simple coffee"

Erstellen Sie Dekoratoren, die dem Kaffee Funktionen hinzufügen:

class MilkDecorator:
    def __init__(self, coffee):
        self.coffee = coffee
    
    def cost(self):
        return self.coffee.cost() + 2
    
    def description(self):
        return self.coffee.description() + " + Milk"

class SugarDecorator:
    def __init__(self, coffee):
        self.coffee = coffee
    
    def cost(self):
        return self.coffee.cost() + 1
    
    def description(self):
        return self.coffee.description() + " + Sugar"

Jeder Decorator umhüllt ein anderes Objekt und fügt seine eigene Funktionalität hinzu.

Verwenden Sie Decorators, um individuell angepassten Kaffee zu erstellen:

# Beginne mit einfachem Kaffee
my_coffee = Coffee()
print(f"{my_coffee.description()}: ${my_coffee.cost()}")

# Milch hinzufügen
my_coffee = MilkDecorator(my_coffee)
print(f"{my_coffee.description()}: ${my_coffee.cost()}")

# Zucker hinzufügen
my_coffee = SugarDecorator(my_coffee)
print(f"{my_coffee.description()}: ${my_coffee.cost()}")

Erstellen Sie ein weiteres Beispiel mit Textformatierung:

class Text:
    def __init__(self, content):
        self.content = content
    
    def render(self):
        return self.content

class BoldDecorator:
    def __init__(self, text):
        self.text = text
    
    def render(self):
        return f"<b>{self.text.render()}</b>"

class ItalicDecorator:
    def __init__(self, text):
        self.text = text
    
    def render(self):
        return f"<i>{self.text.render()}</i>"

# Formatierten Text Schritt für Schritt erstellen
message = Text("Hello World")
message = BoldDecorator(message)
message = ItalicDecorator(message)

print(message.render())

Ausgabe:

Simple coffee: $5
Simple coffee + Milk: $7
Simple coffee + Milk + Sugar: $8
<i><b>Hello World</b></i>

Kernpunkt: Das Decorator-Pattern umhüllt Objekte, um neues Verhalten hinzuzufügen, ohne das ursprüngliche Objekt zu ändern. Jeder Decorator behält eine Referenz auf das umhüllte Objekt bei und fügt seine eigene Funktionalität hinzu. Dies ermöglicht es Ihnen, mehrere Decorator für flexible Merkmalskombinationen zu kombinieren, ohne viele Unterklassen erstellen zu müssen.

challenge icon

Aufgabe

Mittel

In dieser Herausforderung implementieren Sie das Decorator-Entwurfsmuster, um ein flexibles Kaffee-Bestellsystem für ein Café zu erstellen. Das Decorator-Muster ermöglicht es, Verhalten dynamisch zu einzelnen Objekten hinzuzufügen, ohne das Verhalten anderer Objekte derselben Klasse zu beeinflussen.

Sie bauen ein System für ein Café, das Folgendes leisten muss:

  • Verschiedene Arten von Getränken anbieten (Espresso, Dark Roast, House Blend, Decaf)
  • Kunden ermöglichen, Beilagen (Milk, Mocha, Soy, Whipped Cream) zu ihren Getränken hinzuzufügen
  • Die Kosten für Getränke mit hinzugefügten Beilagen berechnen
  1. Vervollständigen Sie die abstrakte Klasse Beverage und ihre konkreten Implementierungen in beverage.py
  2. Implementieren Sie die abstrakte Klasse CondimentDecorator und die konkreten Decorator in condiment_decorator.py
  3. Implementieren Sie Test-Szenarien in driver.py, um Ihre Implementierung zu überprüfen

Spickzettel

Das Decorator-Pattern fügt Objekten dynamisch neue Funktionalität hinzu, ohne deren Struktur zu ändern, indem es Objekte umhüllt, um deren Verhalten zu erweitern.

Grundstruktur mit einem Kaffee-Beispiel:

class Coffee:
    def cost(self):
        return 5
    
    def description(self):
        return "Simple coffee"

Erstellen Sie Decorator, die die Funktionalität umhüllen und erweitern:

class MilkDecorator:
    def __init__(self, coffee):
        self.coffee = coffee
    
    def cost(self):
        return self.coffee.cost() + 2
    
    def description(self):
        return self.coffee.description() + " + Milk"

class SugarDecorator:
    def __init__(self, coffee):
        self.coffee = coffee
    
    def cost(self):
        return self.coffee.cost() + 1
    
    def description(self):
        return self.coffee.description() + " + Sugar"

Verketten Sie Decorator, um angepasste Objekte zu erstellen:

# Mit einfachem Kaffee beginnen
my_coffee = Coffee()

# Decorator hinzufügen
my_coffee = MilkDecorator(my_coffee)
my_coffee = SugarDecorator(my_coffee)

print(f"{my_coffee.description()}: ${my_coffee.cost()}")
# Ausgabe: Simple coffee + Milk + Sugar: $8

Beispiel zur Textformatierung:

class Text:
    def __init__(self, content):
        self.content = content
    
    def render(self):
        return self.content

class BoldDecorator:
    def __init__(self, text):
        self.text = text
    
    def render(self):
        return f"<b>{self.text.render()}</b>"

class ItalicDecorator:
    def __init__(self, text):
        self.text = text
    
    def render(self):
        return f"<i>{self.text.render()}</i>"

# Decorator verketten
message = Text("Hello World")
message = BoldDecorator(message)
message = ItalicDecorator(message)

print(message.render())
# Ausgabe: <i><b>Hello World</b></i>

Hauptvorteile: Ermöglicht flexible Merkmalskombinationen ohne die Erstellung vieler Unterklassen, behält den Verweis auf das umhüllte Objekt bei und fügt Funktionalität dynamisch hinzu.

Probier es selbst

# Importiere alle notwendigen Klassen
from beverage import Espresso, HouseBlend, DarkRoast, Decaf
from condiment_decorator import Milk, Mocha, Soy, Whip

# Umfassender Testfall-Handler
test_case = input()

if test_case == "basic_beverage_test":
    # Teste grundlegende Getränke-Funktionalität
    espresso = Espresso()
    print(f"Description: {espresso.get_description()}")
    print(f"Cost: ${espresso.cost():.2f}")

elif test_case == "all_beverages_test":
    # Teste alle Getränketypen
    beverages = [Espresso(), HouseBlend(), DarkRoast(), Decaf()]
    for beverage in beverages:
        print(f"{beverage.get_description()}: ${beverage.cost():.2f}")

elif test_case == "single_condiment_test":
    # Teste Dekoration mit einer einzelnen Zutat
    espresso = Espresso()
    espresso_with_milk = Milk(espresso)
    print(f"Description: {espresso_with_milk.get_description()}")
    print(f"Cost: ${espresso_with_milk.cost():.2f}")

elif test_case == "multiple_condiments_test":
    # Teste Dekoration mit mehreren Zutaten
    house_blend = HouseBlend()
    decorated_coffee = Mocha(Soy(Whip(house_blend)))
    print(f"Description: {decorated_coffee.get_description()}")
    print(f"Cost: ${decorated_coffee.cost():.2f}")

elif test_case == "all_condiments_test":
    # Teste alle Zutaten auf einem Getränk
    dark_roast = DarkRoast()
    fully_loaded = Milk(Mocha(Soy(Whip(dark_roast))))
    print(f"Description: {fully_loaded.get_description()}")
    print(f"Cost: ${fully_loaded.cost():.2f}")

elif test_case == "cost_calculation_test":
    # Teste Kostenberechnungen für verschiedene Kombinationen
    espresso = Espresso()
    
    # Einzelne Ergänzungen
    with_milk = Milk(espresso)
    with_mocha = Mocha(espresso)
    
    print(f"Espresso: ${espresso.cost():.2f}")
    print(f"Espresso + Milk: ${with_milk.cost():.2f}")
    print(f"Espresso + Mocha: ${with_mocha.cost():.2f}")

elif test_case == "decoration_order_test":
    # Teste, dass die Reihenfolge der Dekoration das Endergebnis nicht beeinflusst
    decaf = Decaf()
    
    order1 = Milk(Mocha(decaf))
    order2 = Mocha(Milk(decaf))
    
    print(f"Order 1 - {order1.get_description()}: ${order1.cost():.2f}")
    print(f"Order 2 - {order2.get_description()}: ${order2.cost():.2f}")
    print(f"Same cost: {order1.cost() == order2.cost()}")

elif test_case == "complex_order_test":
    # Teste komplexe Getränkebestellungen
    orders = [
        Mocha(Whip(Espresso())),
        Soy(Milk(HouseBlend())),
        Whip(Mocha(Soy(DarkRoast()))),
        Milk(Decaf())
    ]
    
    for i, order in enumerate(orders, 1):
        print(f"Order {i}: {order.get_description()}")
        print(f"Cost: ${order.cost():.2f}")
        print("---")

elif test_case == "double_condiment_test":
    # Teste das mehrfache Hinzufügen derselben Zutat
    espresso = Espresso()
    double_mocha = Mocha(Mocha(espresso))
    print(f"Description: {double_mocha.get_description()}")
    print(f"Cost: ${double_mocha.cost():.2f}")

elif test_case == "beverage_comparison_test":
    # Vergleiche Kosten verschiedener Getränke mit denselben Zutaten
    base_beverages = [Espresso(), HouseBlend(), DarkRoast(), Decaf()]
    
    print("All beverages with Milk + Mocha:")
    for beverage in base_beverages:
        decorated = Milk(Mocha(beverage))
        print(f"{decorated.get_description()}: ${decorated.cost():.2f}")

elif test_case == "condiment_cost_test":
    # Teste die Kosten einzelner Zutaten
    espresso = Espresso()
    base_cost = espresso.cost()
    
    condiments = [
        ("Milk", Milk(espresso)),
        ("Mocha", Mocha(espresso)),
        ("Soy", Soy(espresso)),
        ("Whip", Whip(espresso))
    ]
    
    for name, decorated in condiments:
        additional_cost = decorated.cost() - base_cost
        print(f"{name} adds: ${additional_cost:.2f}")

elif test_case == "cheapest_most_expensive_test":
    # Finde das günstigste und das teuerste Basisgetränk
    beverages = [
        ("Espresso", Espresso()),
        ("House Blend", HouseBlend()),
        ("Dark Roast", DarkRoast()),
        ("Decaf", Decaf())
    ]
    
    beverages.sort(key=lambda x: x[1].cost())
    print(f"Cheapest: {beverages[0][0]} - ${beverages[0][1].cost():.2f}")
    print(f"Most Expensive: {beverages[-1][0]} - ${beverages[-1][1].cost():.2f}")

elif test_case == "nested_decoration_test":
    # Teste tief verschachtelte Dekorationen
    beverage = HouseBlend()
    # Füge 5 Schichten von Dekorationen hinzu
    decorated = Whip(Soy(Mocha(Milk(Whip(beverage)))))
    print(f"Description: {decorated.get_description()}")
    print(f"Cost: ${decorated.cost():.2f}")
    
    # Zähle die Anzahl der Zutaten
    description = decorated.get_description()
    condiment_count = description.count(" + ")
    print(f"Number of condiments: {condiment_count}")

elif test_case == "cost_breakdown_test":
    # Detaillierte Kostenaufstellung
    dark_roast = DarkRoast()
    print(f"Base Dark Roast: ${dark_roast.cost():.2f}")
    
    with_soy = Soy(dark_roast)
    print(f"+ Soy: ${with_soy.cost():.2f}")
    
    with_mocha = Mocha(with_soy)
    print(f"+ Mocha: ${with_mocha.cost():.2f}")
    
    with_whip = Whip(with_mocha)
    print(f"+ Whip: ${with_whip.cost():.2f}")
    
    final_cost = with_whip.cost()
    print(f"Final total: ${final_cost:.2f}")

elif test_case == "description_format_test":
    # Teste die Konsistenz der Formatierung der Beschreibung
    espresso = Espresso()
    test_combinations = [
        Milk(espresso),
        Mocha(Milk(espresso)),
        Whip(Mocha(Milk(espresso))),
        Soy(Whip(Mocha(Milk(espresso))))
    ]
    
    for combo in test_combinations:
        description = combo.get_description()
        print(f"Description: '{description}'")
        # Prüfe, ob die Beschreibung mit dem Namen des Basisgetränks beginnt
        starts_correctly = description.startswith("Espresso")
        print(f"Starts with base name: {starts_correctly}")
        print("---")
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 Object Oriented Programming