Skip to Content

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._instance

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:

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'] = SlackNotification

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:

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 $100

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:

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.0

Behavioral 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 OFF

Tổng kết

Bảng tổng hợp các patterns:

PatternLoạiMục đíchKhi nào dùng
SingletonCreationalChỉ một instanceDatabase, Logger, Config
FactoryCreationalTạo objects linh hoạtNhiều loại objects tương tự
BuilderCreationalTạo objects phức tạpNhiều parameters, nhiều bước
AdapterStructuralChuyển đổi interfaceTích hợp third-party, legacy code
DecoratorStructuralThêm chức năng độngLogging, caching, validation
StrategyBehavioralChọn algorithmNhiều cách giải quyết vấn đề
ObserverBehavioralNotify changesEvent handling, pub/sub
CommandBehavioralĐóng gói requestsUndo/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!

Last updated on