Design Patterns
Design Patterns là gì?
Design Patterns là các giải pháp tái sử dụng cho các vấn đề thường gặp trong thiết kế phần mềm. Đây là các “best practices” đã được chứng minh qua thời gian.
“Each pattern describes a problem which occurs over and over again in our environment, and then describes the core of the solution to that problem.” - Christopher Alexander
Phân loại Design Patterns
Design Patterns được chia thành 3 nhóm chính:
1. Creational Patterns (Khởi tạo)
Liên quan đến cách tạo objects.
2. Structural Patterns (Cấu trúc)
Liên quan đến cách tổ chức các class và objects.
3. Behavioral Patterns (Hành vi)
Liên quan đến cách các objects tương tác với nhau.
Creational Patterns
1. Singleton Pattern
Mục đích: Đảm bảo một class chỉ có duy nhất một instance và cung cấp global access point.
Khi nào dùng:
- Database connection
- Logger
- Configuration manager
- Cache
❌ Không dùng Singleton:
class Database:
def __init__(self):
print("Connecting to database...")
self.connection = "DB Connection"
# Tạo nhiều instances
db1 = Database() # Connecting to database...
db2 = Database() # Connecting to database...
db3 = Database() # Connecting to database...
# db1, db2, db3 là 3 objects khác nhau! Lãng phí tài nguyên✅ Dùng Singleton:
class Database:
_instance = None
def __new__(cls):
if cls._instance is None:
print("Connecting to database...")
cls._instance = super().__new__(cls)
cls._instance.connection = "DB Connection"
return cls._instance
# Chỉ tạo một instance
db1 = Database() # Connecting to database...
db2 = Database() # Không in gì
db3 = Database() # Không in gì
print(db1 is db2 is db3) # True - Cùng một instance!Cách khác - Thread-safe Singleton:
import threading
class Database:
_instance = None
_lock = threading.Lock()
def __new__(cls):
if cls._instance is None:
with cls._lock:
if cls._instance is None:
print("Connecting to database...")
cls._instance = super().__new__(cls)
cls._instance.connection = "DB Connection"
return cls._instance2. Factory Pattern
Mục đích: Tạo objects mà không cần chỉ định class cụ thể.
Khi nào dùng:
- Cần tạo objects dựa trên điều kiện
- Muốn ẩn logic khởi tạo phức tạp
❌ Không dùng Factory:
def create_notification(type, message):
if type == "email":
return EmailNotification(message)
elif type == "sms":
return SMSNotification(message)
elif type == "push":
return PushNotification(message)
else:
raise ValueError(f"Unknown type: {type}")
# Logic khởi tạo phân tán khắp nơi✅ Dùng Factory:
from abc import ABC, abstractmethod
# 1. Interface
class Notification(ABC):
@abstractmethod
def send(self, message: str):
pass
# 2. Concrete implementations
class EmailNotification(Notification):
def send(self, message: str):
print(f"Sending email: {message}")
class SMSNotification(Notification):
def send(self, message: str):
print(f"Sending SMS: {message}")
class PushNotification(Notification):
def send(self, message: str):
print(f"Sending push: {message}")
# 3. Factory
class NotificationFactory:
@staticmethod
def create(notification_type: str) -> Notification:
"""Factory method tạo notification."""
notifications = {
'email': EmailNotification,
'sms': SMSNotification,
'push': PushNotification
}
notification_class = notifications.get(notification_type)
if not notification_class:
raise ValueError(f"Unknown notification type: {notification_type}")
return notification_class()
# Sử dụng
notification = NotificationFactory.create('email')
notification.send("Hello!")
# Dễ dàng thêm loại mới
class SlackNotification(Notification):
def send(self, message: str):
print(f"Sending Slack message: {message}")
# Đăng ký với factory
NotificationFactory.notifications['slack'] = SlackNotification3. Builder Pattern
Mục đích: Tách việc khởi tạo object phức tạp thành các bước riêng biệt.
Khi nào dùng:
- Object có nhiều tham số
- Có nhiều cách khởi tạo khác nhau
- Muốn code dễ đọc hơn
❌ Constructor phức tạp:
class Pizza:
def __init__(
self,
size,
cheese=False,
pepperoni=False,
mushrooms=False,
onions=False,
bacon=False,
olives=False
):
# Quá nhiều parameters!
pass
# Khó đọc, dễ nhầm lẫn
pizza = Pizza('large', True, False, True, False, True, False)✅ Dùng Builder:
class Pizza:
def __init__(self):
self.size = None
self.toppings = []
class PizzaBuilder:
def __init__(self):
self.pizza = Pizza()
def set_size(self, size: str):
"""Set pizza size."""
self.pizza.size = size
return self # Return self để chain methods
def add_cheese(self):
"""Add cheese topping."""
self.pizza.toppings.append('cheese')
return self
def add_pepperoni(self):
"""Add pepperoni topping."""
self.pizza.toppings.append('pepperoni')
return self
def add_mushrooms(self):
"""Add mushrooms topping."""
self.pizza.toppings.append('mushrooms')
return self
def build(self) -> Pizza:
"""Build and return the pizza."""
return self.pizza
# Sử dụng - Dễ đọc, rõ ràng
pizza = (PizzaBuilder()
.set_size('large')
.add_cheese()
.add_pepperoni()
.add_mushrooms()
.build())
print(f"Pizza size: {pizza.size}")
print(f"Toppings: {', '.join(pizza.toppings)}")Structural Patterns
4. Adapter Pattern
Mục đích: Chuyển đổi interface của một class thành interface khác mà client mong muốn.
Khi nào dùng:
- Tích hợp third-party libraries
- Làm việc với legacy code
- Cần chuyển đổi interface
Ví dụ:
# External library - Không thể thay đổi
class OldPaymentSystem:
def make_payment(self, amount):
print(f"Old system: Processing ${amount}")
# Interface mới mà chúng ta muốn dùng
from abc import ABC, abstractmethod
class PaymentProcessor(ABC):
@abstractmethod
def process_payment(self, amount: float) -> bool:
pass
# Adapter - Chuyển đổi interface cũ sang interface mới
class OldPaymentAdapter(PaymentProcessor):
def __init__(self, old_system: OldPaymentSystem):
self.old_system = old_system
def process_payment(self, amount: float) -> bool:
"""Adapter chuyển đổi từ interface mới sang cũ."""
self.old_system.make_payment(amount)
return True
# Sử dụng
old_system = OldPaymentSystem()
adapter = OldPaymentAdapter(old_system)
# Client code chỉ biết PaymentProcessor interface
def checkout(processor: PaymentProcessor, amount: float):
processor.process_payment(amount)
checkout(adapter, 100) # Old system: Processing $1005. Decorator Pattern
Mục đích: Thêm chức năng mới vào object mà không thay đổi cấu trúc của nó.
Khi nào dùng:
- Thêm tính năng động
- Logging, caching, validation
- Không muốn dùng inheritance
Ví dụ với Python decorators:
import time
import functools
# Decorator cho logging
def log_execution(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
print(f"Executing {func.__name__}")
result = func(*args, **kwargs)
print(f"Finished {func.__name__}")
return result
return wrapper
# Decorator cho timing
def measure_time(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
start = time.time()
result = func(*args, **kwargs)
end = time.time()
print(f"{func.__name__} took {end - start:.2f} seconds")
return result
return wrapper
# Decorator cho caching
def cache_result(func):
cache = {}
@functools.wraps(func)
def wrapper(*args):
if args in cache:
print(f"Using cached result for {args}")
return cache[args]
result = func(*args)
cache[args] = result
return result
return wrapper
# Sử dụng decorators
@log_execution
@measure_time
@cache_result
def expensive_calculation(n: int) -> int:
"""Calculate sum of numbers from 1 to n."""
time.sleep(1) # Simulate expensive operation
return sum(range(1, n + 1))
# Call lần 1 - Chạy function
result1 = expensive_calculation(100)
# Call lần 2 - Dùng cache
result2 = expensive_calculation(100)Ví dụ với classes:
from abc import ABC, abstractmethod
# Component interface
class Coffee(ABC):
@abstractmethod
def cost(self) -> float:
pass
@abstractmethod
def description(self) -> str:
pass
# Concrete component
class SimpleCoffee(Coffee):
def cost(self) -> float:
return 10.0
def description(self) -> str:
return "Simple coffee"
# Decorator base class
class CoffeeDecorator(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()
# Concrete decorators
class MilkDecorator(CoffeeDecorator):
def cost(self) -> float:
return self._coffee.cost() + 2.0
def description(self) -> str:
return self._coffee.description() + ", milk"
class SugarDecorator(CoffeeDecorator):
def cost(self) -> float:
return self._coffee.cost() + 1.0
def description(self) -> str:
return self._coffee.description() + ", sugar"
class VanillaDecorator(CoffeeDecorator):
def cost(self) -> float:
return self._coffee.cost() + 3.0
def description(self) -> str:
return self._coffee.description() + ", vanilla"
# Sử dụng - Có thể stack nhiều decorators
coffee = SimpleCoffee()
print(f"{coffee.description()}: ${coffee.cost()}")
coffee = MilkDecorator(coffee)
print(f"{coffee.description()}: ${coffee.cost()}")
coffee = SugarDecorator(coffee)
print(f"{coffee.description()}: ${coffee.cost()}")
coffee = VanillaDecorator(coffee)
print(f"{coffee.description()}: ${coffee.cost()}")
# Output:
# Simple coffee: $10.0
# Simple coffee, milk: $12.0
# Simple coffee, milk, sugar: $13.0
# Simple coffee, milk, sugar, vanilla: $16.0Behavioral Patterns
6. Strategy Pattern
Mục đích: Định nghĩa một họ các algorithms, đóng gói từng algorithm và làm chúng có thể thay thế cho nhau.
Khi nào dùng:
- Có nhiều cách giải quyết một vấn đề
- Muốn chọn algorithm tại runtime
- Tránh nhiều if-else
❌ Không dùng Strategy:
class PaymentProcessor:
def process(self, amount: float, method: str):
if method == 'credit_card':
print(f"Processing ${amount} via credit card")
# Credit card logic
elif method == 'paypal':
print(f"Processing ${amount} via PayPal")
# PayPal logic
elif method == 'bitcoin':
print(f"Processing ${amount} via Bitcoin")
# Bitcoin logic
else:
raise ValueError(f"Unknown payment method: {method}")✅ Dùng Strategy:
from abc import ABC, abstractmethod
# Strategy interface
class PaymentStrategy(ABC):
@abstractmethod
def pay(self, amount: float) -> bool:
pass
# Concrete strategies
class CreditCardStrategy(PaymentStrategy):
def __init__(self, card_number: str, cvv: str):
self.card_number = card_number
self.cvv = cvv
def pay(self, amount: float) -> bool:
print(f"Paying ${amount} with credit card {self.card_number[-4:]}")
return True
class PayPalStrategy(PaymentStrategy):
def __init__(self, email: str):
self.email = email
def pay(self, amount: float) -> bool:
print(f"Paying ${amount} with PayPal account {self.email}")
return True
class BitcoinStrategy(PaymentStrategy):
def __init__(self, wallet_address: str):
self.wallet_address = wallet_address
def pay(self, amount: float) -> bool:
print(f"Paying ${amount} with Bitcoin wallet {self.wallet_address}")
return True
# Context
class ShoppingCart:
def __init__(self):
self.items = []
self.payment_strategy = None
def add_item(self, item: dict):
self.items.append(item)
def set_payment_strategy(self, strategy: PaymentStrategy):
"""Set payment strategy at runtime."""
self.payment_strategy = strategy
def checkout(self):
"""Checkout using the selected payment strategy."""
total = sum(item['price'] for item in self.items)
if not self.payment_strategy:
raise ValueError("Payment strategy not set")
return self.payment_strategy.pay(total)
# Sử dụng
cart = ShoppingCart()
cart.add_item({'name': 'Book', 'price': 20})
cart.add_item({'name': 'Pen', 'price': 5})
# Chọn strategy tại runtime
cart.set_payment_strategy(CreditCardStrategy('1234-5678-9012-3456', '123'))
cart.checkout()
# Đổi strategy
cart.set_payment_strategy(PayPalStrategy('user@example.com'))
cart.checkout()7. Observer Pattern
Mục đích: Định nghĩa one-to-many dependency, khi một object thay đổi state, tất cả dependents được thông báo tự động.
Khi nào dùng:
- Event handling
- Pub/Sub systems
- Model-View relationship
- Real-time updates
Ví dụ:
from abc import ABC, abstractmethod
from typing import List
# Observer interface
class Observer(ABC):
@abstractmethod
def update(self, subject: 'Subject'):
pass
# Subject (Observable)
class Subject:
def __init__(self):
self._observers: List[Observer] = []
self._state = None
def attach(self, observer: Observer):
"""Đăng ký observer."""
if observer not in self._observers:
self._observers.append(observer)
def detach(self, observer: Observer):
"""Hủy đăng ký observer."""
self._observers.remove(observer)
def notify(self):
"""Thông báo cho tất cả observers."""
for observer in self._observers:
observer.update(self)
def set_state(self, state):
"""Update state và notify observers."""
self._state = state
self.notify()
def get_state(self):
return self._state
# Concrete observers
class EmailNotifier(Observer):
def update(self, subject: Subject):
state = subject.get_state()
print(f"EmailNotifier: Sending email about state change to {state}")
class SMSNotifier(Observer):
def update(self, subject: Subject):
state = subject.get_state()
print(f"SMSNotifier: Sending SMS about state change to {state}")
class Logger(Observer):
def update(self, subject: Subject):
state = subject.get_state()
print(f"Logger: State changed to {state}")
# Sử dụng
subject = Subject()
# Đăng ký observers
email_notifier = EmailNotifier()
sms_notifier = SMSNotifier()
logger = Logger()
subject.attach(email_notifier)
subject.attach(sms_notifier)
subject.attach(logger)
# Thay đổi state - Tất cả observers được notify
subject.set_state("Order Placed")
print()
subject.set_state("Order Shipped")
print()
# Hủy đăng ký một observer
subject.detach(sms_notifier)
subject.set_state("Order Delivered")8. Command Pattern
Mục đích: Đóng gói request thành object, cho phép parameterize clients với different requests, queue requests, và support undo operations.
Khi nào dùng:
- Undo/Redo functionality
- Queue operations
- Transaction systems
- Macro recording
Ví dụ:
from abc import ABC, abstractmethod
# Command interface
class Command(ABC):
@abstractmethod
def execute(self):
pass
@abstractmethod
def undo(self):
pass
# Receiver - Object thực sự thực hiện công việc
class Light:
def __init__(self, location: str):
self.location = location
self.is_on = False
def turn_on(self):
self.is_on = True
print(f"{self.location} light is ON")
def turn_off(self):
self.is_on = False
print(f"{self.location} light is OFF")
# Concrete commands
class LightOnCommand(Command):
def __init__(self, light: Light):
self.light = light
def execute(self):
self.light.turn_on()
def undo(self):
self.light.turn_off()
class LightOffCommand(Command):
def __init__(self, light: Light):
self.light = light
def execute(self):
self.light.turn_off()
def undo(self):
self.light.turn_on()
# Invoker - Gọi commands
class RemoteControl:
def __init__(self):
self.history = []
def execute_command(self, command: Command):
"""Execute command và lưu vào history."""
command.execute()
self.history.append(command)
def undo_last_command(self):
"""Undo command cuối cùng."""
if self.history:
command = self.history.pop()
command.undo()
else:
print("Nothing to undo")
# Sử dụng
living_room_light = Light("Living room")
bedroom_light = Light("Bedroom")
remote = RemoteControl()
# Thực hiện commands
remote.execute_command(LightOnCommand(living_room_light))
remote.execute_command(LightOnCommand(bedroom_light))
remote.execute_command(LightOffCommand(living_room_light))
print("\nUndo last command:")
remote.undo_last_command() # Living room light is ON
print("\nUndo again:")
remote.undo_last_command() # Bedroom light is OFFTổng kết
Bảng tổng hợp các patterns:
| Pattern | Loại | Mục đích | Khi nào dùng |
|---|---|---|---|
| Singleton | Creational | Chỉ một instance | Database, Logger, Config |
| Factory | Creational | Tạo objects linh hoạt | Nhiều loại objects tương tự |
| Builder | Creational | Tạo objects phức tạp | Nhiều parameters, nhiều bước |
| Adapter | Structural | Chuyển đổi interface | Tích hợp third-party, legacy code |
| Decorator | Structural | Thêm chức năng động | Logging, caching, validation |
| Strategy | Behavioral | Chọn algorithm | Nhiều cách giải quyết vấn đề |
| Observer | Behavioral | Notify changes | Event handling, pub/sub |
| Command | Behavioral | Đóng gói requests | Undo/redo, queue operations |
Lợi ích của Design Patterns:
- ✅ Reusable: Giải pháp đã được chứng minh
- ✅ Communication: Ngôn ngữ chung giữa developers
- ✅ Best practices: Học từ kinh nghiệm của người đi trước
- ✅ Maintainability: Code dễ hiểu và bảo trì
Lưu ý:
- ⚠️ Đừng lạm dụng: Không phải lúc nào cũng cần pattern
- ⚠️ KISS principle: Keep It Simple, Stupid
- ⚠️ YAGNI: You Aren’t Gonna Need It
Design Patterns là công cụ hữu ích, nhưng hãy sử dụng khi thực sự cần thiết!
Python