Design Patterns
Design Patterns là gì?
Phần tiêu đề “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
Phần tiêu đề “Phân loại Design Patterns”Design Patterns được chia thành 3 nhóm chính:
1. Creational Patterns (Khởi tạo)
Phần tiêu đề “1. Creational Patterns (Khởi tạo)”Liên quan đến cách tạo objects.
2. Structural Patterns (Cấu trúc)
Phần tiêu đề “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)
Phần tiêu đề “3. Behavioral Patterns (Hành vi)”Liên quan đến cách các objects tương tác với nhau.
Creational Patterns
Phần tiêu đề “Creational Patterns”1. Singleton Pattern
Phần tiêu đề “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:
Phần tiêu đề “❌ Không dùng Singleton:”class Database: def __init__(self): print("Connecting to database...") self.connection = "DB Connection"
# Tạo nhiều instancesdb1 = 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:
Phần tiêu đề “✅ 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 instancedb1 = 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:
Phần tiêu đề “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
Phần tiêu đề “2. 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:
Phần tiêu đề “❌ 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:
Phần tiêu đề “✅ Dùng Factory:”from abc import ABC, abstractmethod
# 1. Interfaceclass Notification(ABC): @abstractmethod def send(self, message: str): pass
# 2. Concrete implementationsclass 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. Factoryclass 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ụngnotification = NotificationFactory.create('email')notification.send("Hello!")
# Dễ dàng thêm loại mớiclass SlackNotification(Notification): def send(self, message: str): print(f"Sending Slack message: {message}")
# Đăng ký với factoryNotificationFactory.notifications['slack'] = SlackNotification3. Builder Pattern
Phần tiêu đề “3. 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:
Phần tiêu đề “❌ 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ẫnpizza = Pizza('large', True, False, True, False, True, False)✅ Dùng Builder:
Phần tiêu đề “✅ 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àngpizza = (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
Phần tiêu đề “Structural Patterns”4. Adapter Pattern
Phần tiêu đề “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ụ:
Phần tiêu đề “Ví dụ:”# External library - Không thể thay đổiclass OldPaymentSystem: def make_payment(self, amount): print(f"Old system: Processing ${amount}")
# Interface mới mà chúng ta muốn dùngfrom 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ớiclass 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ụngold_system = OldPaymentSystem()adapter = OldPaymentAdapter(old_system)
# Client code chỉ biết PaymentProcessor interfacedef checkout(processor: PaymentProcessor, amount: float): processor.process_payment(amount)
checkout(adapter, 100) # Old system: Processing $1005. Decorator Pattern
Phần tiêu đề “5. 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:
Phần tiêu đề “Ví dụ với Python decorators:”import timeimport functools
# Decorator cho loggingdef 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 timingdef 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 cachingdef 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_resultdef 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 functionresult1 = expensive_calculation(100)
# Call lần 2 - Dùng cacheresult2 = expensive_calculation(100)Ví dụ với classes:
Phần tiêu đề “Ví dụ với classes:”from abc import ABC, abstractmethod
# Component interfaceclass Coffee(ABC): @abstractmethod def cost(self) -> float: pass
@abstractmethod def description(self) -> str: pass
# Concrete componentclass SimpleCoffee(Coffee): def cost(self) -> float: return 10.0
def description(self) -> str: return "Simple coffee"
# Decorator base classclass 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 decoratorsclass 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 decoratorscoffee = 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
Phần tiêu đề “Behavioral Patterns”6. Strategy Pattern
Phần tiêu đề “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:
Phần tiêu đề “❌ 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:
Phần tiêu đề “✅ Dùng Strategy:”from abc import ABC, abstractmethod
# Strategy interfaceclass PaymentStrategy(ABC): @abstractmethod def pay(self, amount: float) -> bool: pass
# Concrete strategiesclass 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
# Contextclass 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ụngcart = ShoppingCart()cart.add_item({'name': 'Book', 'price': 20})cart.add_item({'name': 'Pen', 'price': 5})
# Chọn strategy tại runtimecart.set_payment_strategy(CreditCardStrategy('1234-5678-9012-3456', '123'))cart.checkout()
# Đổi strategycart.checkout()7. Observer Pattern
Phần tiêu đề “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ụ:
Phần tiêu đề “Ví dụ:”from abc import ABC, abstractmethodfrom typing import List
# Observer interfaceclass 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 observersclass 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ụngsubject = Subject()
# Đăng ký observersemail_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 notifysubject.set_state("Order Placed")print()
subject.set_state("Order Shipped")print()
# Hủy đăng ký một observersubject.detach(sms_notifier)subject.set_state("Order Delivered")8. Command Pattern
Phần tiêu đề “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ụ:
Phần tiêu đề “Ví dụ:”from abc import ABC, abstractmethod
# Command interfaceclass Command(ABC): @abstractmethod def execute(self): pass
@abstractmethod def undo(self): pass
# Receiver - Object thực sự thực hiện công việcclass 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 commandsclass 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 commandsclass 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ụngliving_room_light = Light("Living room")bedroom_light = Light("Bedroom")
remote = RemoteControl()
# Thực hiện commandsremote.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
Phần tiêu đề “Tổng kết”Bảng tổng hợp các patterns:
Phần tiêu đề “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:
Phần tiêu đề “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 ý:
Phần tiêu đề “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!