Claude Artifact

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:

  1. 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
    
    
  2. 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
    
    
  3. Key Benefits:

Would you like to:

  1. See how to implement caching decorators?
  2. Add more complex coffee customizations?
  3. Explore how Python's built-in decorators work?