Good morning! Let's explore the Decorator Pattern, which is extensively used in Python's I/O classes and allows us to add behaviors to objects dynamically.
from abc import ABC, abstractmethod
from typing import List
# Base Component Interface
class Coffee(ABC):
@abstractmethod
def get_cost(self) -> float:
pass
@abstractmethod
def get_description(self) -> str:
pass
@abstractmethod
def get_ingredients(self) -> List[str]:
pass
# Concrete Components (Basic Coffees)
class Espresso(Coffee):
def get_cost(self) -> float:
return 2.00
def get_description(self) -> str:
return "Espresso"
def get_ingredients(self) -> List[str]:
return ["Coffee Beans"]
class HouseBlend(Coffee):
def get_cost(self) -> float:
return 1.50
def get_description(self) -> str:
return "House Blend Coffee"
def get_ingredients(self) -> List[str]:
return ["Coffee Beans", "Hot Water"]
# Base Decorator
class CoffeeDecorator(Coffee):
def __init__(self, coffee: Coffee):
self._coffee = coffee
def get_cost(self) -> float:
return self._coffee.get_cost()
def get_description(self) -> str:
return self._coffee.get_description()
def get_ingredients(self) -> List[str]:
return self._coffee.get_ingredients()
# Concrete Decorators
class Milk(CoffeeDecorator):
def get_cost(self) -> float:
return self._coffee.get_cost() + 0.50
def get_description(self) -> str:
return f"{self._coffee.get_description()}, Milk"
def get_ingredients(self) -> List[str]:
return self._coffee.get_ingredients() + ["Milk"]
class WhippedCream(CoffeeDecorator):
def get_cost(self) -> float:
return self._coffee.get_cost() + 0.75
def get_description(self) -> str:
return f"{self._coffee.get_description()}, Whipped Cream"
def get_ingredients(self) -> List[str]:
return self._coffee.get_ingredients() + ["Whipped Cream"]
class Caramel(CoffeeDecorator):
def get_cost(self) -> float:
return self._coffee.get_cost() + 0.60
def get_description(self) -> str:
return f"{self._coffee.get_description()}, Caramel"
def get_ingredients(self) -> List[str]:
return self._coffee.get_ingredients() + ["Caramel Syrup"]
class ExtraShot(CoffeeDecorator):
def get_cost(self) -> float:
return self._coffee.get_cost() + 1.00
def get_description(self) -> str:
return f"{self._coffee.get_description()}, Extra Shot"
def get_ingredients(self) -> List[str]:
return self._coffee.get_ingredients() + ["Extra Coffee Shot"]
# Order Manager
class CoffeeOrder:
def __init__(self):
self.items: List[Coffee] = []
def add_coffee(self, coffee: Coffee) -> None:
self.items.append(coffee)
def get_total_cost(self) -> float:
return sum(item.get_cost() for item in self.items)
def print_receipt(self) -> None:
print("\\\\n=== Coffee Order Receipt ===")
for i, coffee in enumerate(self.items, 1):
print(f"\\\\nItem {i}:")
print(f"Description: {coffee.get_description()}")
print(f"Ingredients: {', '.join(coffee.get_ingredients())}")
print(f"Cost: ${coffee.get_cost():.2f}")
print(f"\\\\nTotal: ${self.get_total_cost():.2f}")
print("=========================")
def main():
# Create an order
order = CoffeeOrder()
# Order 1: Espresso with extra shot and whipped cream
coffee1 = Espresso()
coffee1 = ExtraShot(coffee1)
coffee1 = WhippedCream(coffee1)
order.add_coffee(coffee1)
# Order 2: House Blend with milk and caramel
coffee2 = HouseBlend()
coffee2 = Milk(coffee2)
coffee2 = Caramel(coffee2)
order.add_coffee(coffee2)
# Print the receipt
order.print_receipt()
# Demonstrate dynamic nature
print("\\\\nCreating a complex coffee order...")
complex_coffee = Espresso()
complex_coffee = ExtraShot(complex_coffee)
complex_coffee = Milk(complex_coffee)
complex_coffee = Caramel(complex_coffee)
complex_coffee = WhippedCream(complex_coffee)
print(f"\\\\nComplex Coffee Details:")
print(f"Description: {complex_coffee.get_description()}")
print(f"Total Cost: ${complex_coffee.get_cost():.2f}")
print(f"Ingredients: {', '.join(complex_coffee.get_ingredients())}")
if __name__ == "__main__":
main()
The Decorator Pattern is powerful because it:
Adds Behavior Dynamically:
# Each decorator wraps the object and adds functionality
my_coffee = Espresso()
my_coffee = Milk(my_coffee) # Now it has milk
my_coffee = Caramel(my_coffee) # Now it has caramel too
Real-world Python Examples:
# Python's I/O classes use decorators
from io import BufferedReader, TextIOWrapper
file = open('file.txt', 'rb') # Creates a FileIO object
buffered = BufferedReader(file) # Decorates with buffering
text = TextIOWrapper(buffered) # Decorates with text encoding
Key Benefits:
Would you like to: