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.
Aufgabe
MittelIn 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
- Vervollständigen Sie die abstrakte Klasse
Beverageund ihre konkreten Implementierungen inbeverage.py - Implementieren Sie die abstrakte Klasse
CondimentDecoratorund die konkreten Decorator incondiment_decorator.py - 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: $8Beispiel 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("---")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
1Grundlagen der OOP
Externe DateienEinführung in die OOPKlassen vs. ObjekteDer self-ParameterMethodenAttributeKonstruktor-Methode (__init__)Zusammenfassung – Einfacher Taschenrechner4Vererbung
Grundlagen der VererbungDie super()-FunktionMethoden überschreibenMehrfachvererbungMethod Resolution OrderZusammenfassung - Mitarbeiter-Hierarchie7Spezielle Methoden
Einführung in Magic MethodsOperator-ÜberladungMagic Methods für ContainerRückblick - Eigene Liste10Entwurfsmuster Teil 1
Einführung in EntwurfsmusterSingleton-MusterFactory-MusterObserver-MusterStrategy-Muster13Abschlussherausforderungen
E-Learning-PlattformBanksystemSpielcharakter-EntwicklungFahrzeugvermietung2Dekoratoren
Einführung in DekoratorenProperty-DekoratorStatischer Methoden-DekoratorKlassenmethoden-Dekorator5Polymorphismus
Methoden-Überschreiben vertieftDuck TypingAbstrakte Klassen und MethodenInterface-DesignZusammenfassung – Formen-Rechner8Fortgeschrittene OOP-Konzepte
Komposition vs. VererbungMixinsStatische und KlassenmethodenKlassendekoratorenContext Manager11Entwurfsmuster Teil 2
Command-MusterAdapter-MusterDecorator-MusterTemplate-Method-MusterZustandsmusterKompositum-Muster3Klasseneigenschaften
Instanz- vs. KlassenvariablenProperty-DekoratorenPrivate AttributeZusammenfassung – Bankkonto-Manager6Kapselung
Public, Protected, Private MemberZugriffsmodifikatorenInformation HidingProperty-Decorators für FortgeschritteneZusammenfassung - Studentenverwaltungssystem12Projekt: Bibliotheksverwaltung
ProjektübersichtBuch- und Benutzerklassen