python
examples
examples.py🐍python
"""
Design Patterns in Python - Examples
Design patterns are reusable solutions to common problems.
Python's dynamic nature makes many patterns simpler than in other languages.
"""
# =============================================================================
# CREATIONAL PATTERNS - How objects are created
# =============================================================================
# -----------------------------------------------------------------------------
# 1. Singleton Pattern - Ensure only one instance exists
# -----------------------------------------------------------------------------
class SingletonMeta(type):
"""Metaclass implementation of Singleton."""
_instances = {}
def __call__(cls, *args, **kwargs):
if cls not in cls._instances:
cls._instances[cls] = super().__call__(*args, **kwargs)
return cls._instances[cls]
class Database(metaclass=SingletonMeta):
"""Database connection - only one instance needed."""
def __init__(self):
self.connection = "Connected to database"
print("Database initialized")
def query(self, sql: str) -> str:
return f"Executing: {sql}"
# Pythonic Singleton - Using module-level instance
class _AppConfig:
"""Configuration singleton using module pattern."""
def __init__(self):
self.debug = False
self.database_url = "sqlite:///app.db"
self.secret_key = "change-me"
# Module-level singleton instance
config = _AppConfig()
# -----------------------------------------------------------------------------
# 2. Factory Pattern - Create objects without specifying exact class
# -----------------------------------------------------------------------------
from abc import ABC, abstractmethod
class Animal(ABC):
"""Abstract base class for animals."""
@abstractmethod
def speak(self) -> str:
pass
class Dog(Animal):
def speak(self) -> str:
return "Woof!"
class Cat(Animal):
def speak(self) -> str:
return "Meow!"
class Bird(Animal):
def speak(self) -> str:
return "Tweet!"
class AnimalFactory:
"""Factory for creating animals."""
_animals = {
"dog": Dog,
"cat": Cat,
"bird": Bird
}
@classmethod
def create(cls, animal_type: str) -> Animal:
"""Create an animal by type."""
animal_class = cls._animals.get(animal_type.lower())
if not animal_class:
raise ValueError(f"Unknown animal: {animal_type}")
return animal_class()
@classmethod
def register(cls, name: str, animal_class: type):
"""Register a new animal type."""
cls._animals[name.lower()] = animal_class
# Simple Factory Function (more Pythonic)
def create_animal(animal_type: str) -> Animal:
"""Simple factory function."""
factories = {
"dog": Dog,
"cat": Cat,
"bird": Bird
}
return factories[animal_type.lower()]()
# -----------------------------------------------------------------------------
# 3. Builder Pattern - Construct complex objects step by step
# -----------------------------------------------------------------------------
class Pizza:
"""A pizza built step by step."""
def __init__(self):
self.size = "medium"
self.cheese = True
self.toppings: list[str] = []
self.crust = "regular"
def __str__(self):
return (f"{self.size} pizza with {self.crust} crust, "
f"{'cheese, ' if self.cheese else ''}"
f"toppings: {', '.join(self.toppings) or 'none'}")
class PizzaBuilder:
"""Builder for creating pizzas."""
def __init__(self):
self.pizza = Pizza()
def set_size(self, size: str) -> 'PizzaBuilder':
self.pizza.size = size
return self
def add_cheese(self, cheese: bool = True) -> 'PizzaBuilder':
self.pizza.cheese = cheese
return self
def add_topping(self, topping: str) -> 'PizzaBuilder':
self.pizza.toppings.append(topping)
return self
def set_crust(self, crust: str) -> 'PizzaBuilder':
self.pizza.crust = crust
return self
def build(self) -> Pizza:
return self.pizza
# -----------------------------------------------------------------------------
# 4. Prototype Pattern - Clone existing objects
# -----------------------------------------------------------------------------
import copy
class Document:
"""A document that can be cloned."""
def __init__(self, title: str, content: str, metadata: dict | None = None):
self.title = title
self.content = content
self.metadata = metadata or {}
def clone(self) -> 'Document':
"""Create a deep copy of the document."""
return copy.deepcopy(self)
def __str__(self):
return f"Document('{self.title}')"
# =============================================================================
# STRUCTURAL PATTERNS - How objects are composed
# =============================================================================
# -----------------------------------------------------------------------------
# 5. Adapter Pattern - Make incompatible interfaces work together
# -----------------------------------------------------------------------------
class OldPaymentSystem:
"""Legacy payment system with old interface."""
def process_payment_old(self, amount: float, account: str) -> bool:
print(f"OLD: Processing ${amount} for account {account}")
return True
class NewPaymentInterface(ABC):
"""New payment interface expected by the application."""
@abstractmethod
def pay(self, amount: float, user_id: str) -> dict:
pass
class PaymentAdapter(NewPaymentInterface):
"""Adapter to make old system work with new interface."""
def __init__(self, old_system: OldPaymentSystem):
self.old_system = old_system
def pay(self, amount: float, user_id: str) -> dict:
# Adapt the interface
success = self.old_system.process_payment_old(amount, f"USER-{user_id}")
return {"success": success, "amount": amount, "user_id": user_id}
# -----------------------------------------------------------------------------
# 6. Decorator Pattern (Structural, not Python decorator)
# -----------------------------------------------------------------------------
class Coffee(ABC):
"""Abstract coffee interface."""
@abstractmethod
def cost(self) -> float:
pass
@abstractmethod
def description(self) -> str:
pass
class SimpleCoffee(Coffee):
"""Basic coffee."""
def cost(self) -> float:
return 2.0
def description(self) -> str:
return "Coffee"
class CoffeeDecorator(Coffee):
"""Base decorator for coffee."""
def __init__(self, coffee: Coffee):
self._coffee = coffee
def cost(self) -> float:
return self._coffee.cost()
def description(self) -> str:
return self._coffee.description()
class MilkDecorator(CoffeeDecorator):
"""Add milk to coffee."""
def cost(self) -> float:
return self._coffee.cost() + 0.5
def description(self) -> str:
return self._coffee.description() + " + milk"
class SugarDecorator(CoffeeDecorator):
"""Add sugar to coffee."""
def cost(self) -> float:
return self._coffee.cost() + 0.25
def description(self) -> str:
return self._coffee.description() + " + sugar"
# -----------------------------------------------------------------------------
# 7. Facade Pattern - Simplified interface to complex subsystem
# -----------------------------------------------------------------------------
class CPU:
def freeze(self): print("CPU: Freezing...")
def jump(self, address: int): print(f"CPU: Jumping to {address}")
def execute(self): print("CPU: Executing...")
class Memory:
def load(self, address: int, data: bytes):
print(f"Memory: Loading data at {address}")
class HardDrive:
def read(self, sector: int, size: int) -> bytes:
print(f"HardDrive: Reading sector {sector}")
return b"boot data"
class ComputerFacade:
"""Facade that simplifies computer startup."""
def __init__(self):
self.cpu = CPU()
self.memory = Memory()
self.hard_drive = HardDrive()
def start(self):
"""Simple interface to start the computer."""
print("Starting computer...")
self.cpu.freeze()
boot_data = self.hard_drive.read(0, 1024)
self.memory.load(0, boot_data)
self.cpu.jump(0)
self.cpu.execute()
print("Computer started!")
# -----------------------------------------------------------------------------
# 8. Proxy Pattern - Control access to an object
# -----------------------------------------------------------------------------
class ExpensiveResource(ABC):
"""Interface for an expensive resource."""
@abstractmethod
def operation(self) -> str:
pass
class RealExpensiveResource(ExpensiveResource):
"""The actual expensive resource."""
def __init__(self):
print("Creating expensive resource (slow)...")
import time
time.sleep(0.1) # Simulate slow creation
self.data = "Expensive data loaded"
def operation(self) -> str:
return self.data
class LazyProxy(ExpensiveResource):
"""Proxy that delays resource creation until needed."""
def __init__(self):
self._resource: RealExpensiveResource | None = None
def operation(self) -> str:
if self._resource is None:
self._resource = RealExpensiveResource()
return self._resource.operation()
# =============================================================================
# BEHAVIORAL PATTERNS - How objects communicate
# =============================================================================
# -----------------------------------------------------------------------------
# 9. Observer Pattern - Notify dependents of state changes
# -----------------------------------------------------------------------------
class Subject:
"""Subject that observers watch."""
def __init__(self):
self._observers: list[callable] = []
self._state = None
def attach(self, observer: callable):
"""Attach an observer."""
self._observers.append(observer)
def detach(self, observer: callable):
"""Detach an observer."""
self._observers.remove(observer)
def notify(self):
"""Notify all observers."""
for observer in self._observers:
observer(self._state)
@property
def state(self):
return self._state
@state.setter
def state(self, value):
self._state = value
self.notify()
# Pythonic Observer using callbacks
class EventEmitter:
"""Simple event emitter pattern."""
def __init__(self):
self._events: dict[str, list[callable]] = {}
def on(self, event: str, callback: callable):
"""Subscribe to an event."""
if event not in self._events:
self._events[event] = []
self._events[event].append(callback)
def emit(self, event: str, *args, **kwargs):
"""Emit an event to all subscribers."""
for callback in self._events.get(event, []):
callback(*args, **kwargs)
# -----------------------------------------------------------------------------
# 10. Strategy Pattern - Interchangeable algorithms
# -----------------------------------------------------------------------------
from typing import Callable
# Pythonic Strategy: Just use functions!
def sort_by_price(products: list[dict]) -> list[dict]:
return sorted(products, key=lambda p: p['price'])
def sort_by_name(products: list[dict]) -> list[dict]:
return sorted(products, key=lambda p: p['name'])
def sort_by_rating(products: list[dict]) -> list[dict]:
return sorted(products, key=lambda p: p['rating'], reverse=True)
class ProductSorter:
"""Context that uses a sorting strategy."""
def __init__(self, strategy: Callable[[list], list] = sort_by_price):
self.strategy = strategy
def sort(self, products: list[dict]) -> list[dict]:
return self.strategy(products)
# -----------------------------------------------------------------------------
# 11. Command Pattern - Encapsulate actions as objects
# -----------------------------------------------------------------------------
class Command(ABC):
"""Abstract command interface."""
@abstractmethod
def execute(self):
pass
@abstractmethod
def undo(self):
pass
class TextEditor:
"""Receiver - the object that performs the action."""
def __init__(self):
self.text = ""
def insert(self, text: str, position: int):
self.text = self.text[:position] + text + self.text[position:]
def delete(self, position: int, length: int) -> str:
deleted = self.text[position:position + length]
self.text = self.text[:position] + self.text[position + length:]
return deleted
class InsertCommand(Command):
"""Command to insert text."""
def __init__(self, editor: TextEditor, text: str, position: int):
self.editor = editor
self.text = text
self.position = position
def execute(self):
self.editor.insert(self.text, self.position)
def undo(self):
self.editor.delete(self.position, len(self.text))
class CommandHistory:
"""Invoker that manages command history."""
def __init__(self):
self._history: list[Command] = []
def execute(self, command: Command):
command.execute()
self._history.append(command)
def undo(self):
if self._history:
command = self._history.pop()
command.undo()
# -----------------------------------------------------------------------------
# 12. State Pattern - Object behavior changes with state
# -----------------------------------------------------------------------------
class OrderState(ABC):
"""Abstract state for an order."""
@abstractmethod
def proceed(self, order: 'Order'):
pass
@abstractmethod
def cancel(self, order: 'Order'):
pass
class PendingState(OrderState):
def proceed(self, order: 'Order'):
print("Order confirmed, processing payment...")
order.state = ProcessingState()
def cancel(self, order: 'Order'):
print("Order cancelled")
order.state = CancelledState()
class ProcessingState(OrderState):
def proceed(self, order: 'Order'):
print("Payment received, shipping...")
order.state = ShippedState()
def cancel(self, order: 'Order'):
print("Cannot cancel, order is processing")
class ShippedState(OrderState):
def proceed(self, order: 'Order'):
print("Order delivered!")
order.state = DeliveredState()
def cancel(self, order: 'Order'):
print("Cannot cancel, order already shipped")
class DeliveredState(OrderState):
def proceed(self, order: 'Order'):
print("Order already delivered")
def cancel(self, order: 'Order'):
print("Cannot cancel, order delivered")
class CancelledState(OrderState):
def proceed(self, order: 'Order'):
print("Order was cancelled")
def cancel(self, order: 'Order'):
print("Order already cancelled")
class Order:
"""Context - order with changing states."""
def __init__(self):
self.state: OrderState = PendingState()
def proceed(self):
self.state.proceed(self)
def cancel(self):
self.state.cancel(self)
# =============================================================================
# DEMONSTRATIONS
# =============================================================================
if __name__ == "__main__":
print("=" * 60)
print("SINGLETON PATTERN")
print("=" * 60)
db1 = Database()
db2 = Database()
print(f"Same instance? {db1 is db2}") # True
print("\n" + "=" * 60)
print("FACTORY PATTERN")
print("=" * 60)
dog = AnimalFactory.create("dog")
cat = AnimalFactory.create("cat")
print(f"Dog says: {dog.speak()}")
print(f"Cat says: {cat.speak()}")
print("\n" + "=" * 60)
print("BUILDER PATTERN")
print("=" * 60)
pizza = (PizzaBuilder()
.set_size("large")
.set_crust("thin")
.add_topping("pepperoni")
.add_topping("mushrooms")
.build())
print(pizza)
print("\n" + "=" * 60)
print("DECORATOR PATTERN")
print("=" * 60)
coffee = SimpleCoffee()
coffee = MilkDecorator(coffee)
coffee = SugarDecorator(coffee)
print(f"{coffee.description()}: ${coffee.cost()}")
print("\n" + "=" * 60)
print("OBSERVER PATTERN")
print("=" * 60)
emitter = EventEmitter()
emitter.on("user_login", lambda user: print(f"User logged in: {user}"))
emitter.on("user_login", lambda user: print(f"Sending welcome email to {user}"))
emitter.emit("user_login", "alice@example.com")
print("\n" + "=" * 60)
print("STRATEGY PATTERN")
print("=" * 60)
products = [
{"name": "Laptop", "price": 999, "rating": 4.5},
{"name": "Phone", "price": 599, "rating": 4.8},
{"name": "Tablet", "price": 399, "rating": 4.2},
]
sorter = ProductSorter(sort_by_rating)
sorted_products = sorter.sort(products)
print("Sorted by rating:", [p['name'] for p in sorted_products])
print("\n" + "=" * 60)
print("STATE PATTERN")
print("=" * 60)
order = Order()
order.proceed() # Pending -> Processing
order.proceed() # Processing -> Shipped
order.cancel() # Cannot cancel
order.proceed() # Shipped -> Delivered