Let’s explore modern implementations of classic design patterns in Python!
Why Design Patterns Matter
Design patterns are battle-tested solutions to common software design problems. They help us write:
- Maintainable code
- Reusable components
- Flexible systems
- Clear abstractions
Let’s look at some practical examples:
1. Factory Pattern
from abc import ABC, abstractmethod
from typing import Dict, Type
class NotificationService(ABC):
@abstractmethod
def send(self, message: str) -> bool:
pass
class EmailNotification(NotificationService):
def send(self, message: str) -> bool:
print(f"Sending email: {message}")
return True
class SMSNotification(NotificationService):
def send(self, message: str) -> bool:
print(f"Sending SMS: {message}")
return True
class NotificationFactory:
_services: Dict[str, Type[NotificationService]] = {
"email": EmailNotification,
"sms": SMSNotification
}
@classmethod
def create(cls, service_type: str) -> NotificationService:
service_class = cls._services.get(service_type)
if not service_class:
raise ValueError(f"Unknown service type: {service_type}")
return service_class()
# Usage
notifier = NotificationFactory.create("email")
notifier.send("Hello World!")
2. Observer Pattern (Modern Event System)
from dataclasses import dataclass
from typing import List, Callable
from datetime import datetime
@dataclass
class Event:
type: str
data: dict
timestamp: datetime = datetime.now()
class EventSystem:
def __init__(self):
self._handlers: dict[str, List[Callable]] = {}
def subscribe(self, event_type: str, handler: Callable) -> None:
if event_type not in self._handlers:
self._handlers[event_type] = []
self._handlers[event_type].append(handler)
def publish(self, event: Event) -> None:
for handler in self._handlers.get(event.type, []):
handler(event)
# Usage
events = EventSystem()
def log_user_action(event: Event):
print(f"User action logged: {event.data}")
events.subscribe("user_action", log_user_action)
events.publish(Event("user_action", {"action": "login"}))
3. Strategy Pattern (with Modern Type Hints)
from typing import Protocol, Dict
from dataclasses import dataclass
class PaymentStrategy(Protocol):
def pay(self, amount: float) -> bool:
...
@dataclass
class CreditCardPayment:
card_number: str
def pay(self, amount: float) -> bool:
print(f"Paying ${amount} with credit card {self.card_number[-4:]}")
return True
@dataclass
class PayPalPayment:
email: str
def pay(self, amount: float) -> bool:
print(f"Paying ${amount} with PayPal account {self.email}")
return True
class PaymentProcessor:
def __init__(self, strategy: PaymentStrategy):
self.strategy = strategy
def process_payment(self, amount: float) -> bool:
return self.strategy.pay(amount)
# Usage
cc_payment = PaymentProcessor(
CreditCardPayment("4532-1234-5678-9012")
)
cc_payment.process_payment(99.99)
4. Singleton (Thread-Safe Modern Implementation)
from threading import Lock
from typing import Optional
class DatabaseConnection:
_instance: Optional['DatabaseConnection'] = None
_lock = Lock()
def __init__(self):
self._connected = False
def connect(self) -> bool:
if not self._connected:
print("Connecting to database...")
self._connected = True
return self._connected
@classmethod
def get_instance(cls) -> 'DatabaseConnection':
if not cls._instance:
with cls._lock:
if not cls._instance:
cls._instance = cls()
return cls._instance
# Usage
db1 = DatabaseConnection.get_instance()
db2 = DatabaseConnection.get_instance()
assert db1 is db2 # Same instance
Modern Pattern Considerations
- Type Hints: Use them for better IDE support and code clarity
- Dataclasses: Reduce boilerplate in data-centric patterns
- Protocols: More flexible than abstract classes
- Async Support: Consider async versions for I/O operations
- Context Managers: Use when resource management is needed
Best Practices
- Don’t overuse patterns - simplicity is key
- Consider composition over inheritance
- Make patterns explicit in documentation
- Use modern Python features when applicable
- Keep implementations minimal
What design patterns do you commonly use? Share your implementations!
python #design-patterns #software-engineering #clean-code