
Design Pattern phổ biến: Factory, Singleton, Observer áp dụng thực tế
Design Pattern là những giải pháp thiết kế đã được kiểm chứng và tối ưu hóa cho các vấn đề lặp đi lặp lại trong phát triển phần mềm. Thay vì phải tự mò mẫm giải quyết mỗi lần gặp vấn đề thiết kế, lập trình viên có thể áp dụng các pattern đã được khai báo để tiết kiệm thời gian, giảm lỗi và nâng cao tính bảo trì của code. Trong bài viết này, chúng ta sẽ đi sâu vào ba design pattern phổ biến nhất: Factory Method, Singleton và Observer — kèm ví dụ code thực tế và phân tích khi nào nên dùng pattern nào.
Hiểu rõ các pattern này không chỉ giúp bạn viết code sạch hơn mà còn mở khóa cánh cửa vào các khái niệm kiến trúc phần mềm nâng cao như SOLID, Clean Architecture và Microservices. Đây là kiến thức cơ bản mà bất kỳ lập trình viên backend hay full-stack nào cũng cần nắm vững.

Factory Method Pattern: Tạo object mà không cần chỉ rõ class
Factory Method là một creational pattern cung cấp một interface để tạo object trong lớp cha, nhưng để lớp con quyết định instance nào sẽ được khởi tạo. Pattern này giúp code không phụ thuộc vào concrete class cụ thể — một nguyên tắc cốt lõi của Dependency Inversion Principle trong SOLID.
Khi nào dùng Factory Method?
- Khi bạn không biết trước class cụ thể mà object sẽ thuộc về
- Khi bạn muốn delegate việc tạo object cho các lớp con
- Khi bạn cần tái sử dụng object hiện có thay vì tạo mới
Ví dụ thực tế: Payment Gateway Factory
Giả sử bạn đang xây dựng một hệ thống thanh toán hỗ trợ nhiều cổng thanh toán khác nhau (MoMo, ZaloPay, VNPay, Stripe). Thay vì viết if/else hoặc switch lớn trong mỗi hàm xử lý, bạn có thể dùng Factory Method:
from abc import ABC, abstractmethod
# Abstract Product
class PaymentGateway(ABC):
@abstractmethod
def pay(self, amount: float) -> bool:
pass
# Concrete Products
class MoMoGateway(PaymentGateway):
def pay(self, amount: float) -> bool:
print(f"MoMo: Thanh toan {amount} VND")
return True
class ZaloPayGateway(PaymentGateway):
def pay(self, amount: float) -> bool:
print(f"ZaloPay: Thanh toan {amount} VND")
return True
# Creator
class PaymentFactory:
def create_gateway(self, method: str) -> PaymentGateway:
if method == "momo":
return MoMoGateway()
elif method == "zalopay":
return ZaloPayGateway()
raise ValueError(f"Khong ho tro phuong thuc {method}")
# Usage
factory = PaymentFactory()
gateway = factory.create_gateway("momo")
gateway.pay(100000)
Ưu điểm: Khi thêm cổng thanh toán mới (ví dụ VNPay), bạn chỉ cần thêm class mới và sửa một chút trong factory — không cần sửa toàn bộ các hàm xử lý thanh toán khác.
Singleton Pattern: Chỉ một instance duy nhất
Singleton đảm bảo một class chỉ có một instance duy nhất trong toàn bộ ứng dụng và cung cấp một điểm truy cập global đến instance đó. Pattern này thường dùng cho các tài nguyên chia sẻ như database connection pool, logger, hoặc configuration manager.
Khi nào dùng Singleton?
- Khi bạn cần kiểm soát chính xác số lượng instance (chỉ 1)
- Khi tài nguyên quá tốn kém để khởi tạo nhiều lần
- Khi cần một điểm truy cập thống nhất toàn ứng dụng
Ví dụ thực tế: Database Connection Manager
class DatabaseManager:
_instance = None
def __new__(cls):
if cls._instance is None:
cls._instance = super().__new__(cls)
cls._instance.connection = None
return cls._instance
def connect(self, dsn: str):
if self.connection is None:
self.connection = f"Connected to {dsn}"
return self.connection
# Usage
db1 = DatabaseManager()
db2 = DatabaseManager()
print(db1 is db2) # True - cùng instance
Lưu ý: Trong đa luồng, cần dùng threading.Lock hoặc double-checked locking để tránh race condition khi khởi tạo instance.

Observer Pattern: Thông báo khi dữ liệu thay đổi
Observer là một behavioral pattern định nghĩa mối quan hệ một-nhiều giữa các object: khi một object (Subject) thay đổi trạng thái, tất cả các object phụ thuộc (Observers) của nó sẽ được thông báo và cập nhật tự động. Pattern này là nền tảng của cơ chế event-driven và reactive programming.
Khi nào dùng Observer?
- Khi thay đổi ở một object cần thông báo cho nhiều object khác
- Khi bạn muốn loose coupling giữa Subject và Observers
- Khi xây dựng hệ thống event-driven, message queue, hoặc real-time notification
Ví dụ thực tế: Stock Price Notifier
from abc import ABC, abstractmethod
# Observer interface
class PriceObserver(ABC):
@abstractmethod
def update(self, symbol: str, price: float):
pass
# Subject
class StockTicker:
def __init__(self):
self._observers = []
self._prices = {}
def attach(self, observer: PriceObserver):
self._observers.append(observer)
def set_price(self, symbol: str, price: float):
self._prices[symbol] = price
self._notify(symbol, price)
def _notify(self, symbol: str, price: float):
for observer in self._observers:
observer.update(symbol, price)
# Concrete Observers
class EmailAlert(PriceObserver):
def update(self, symbol: str, price: float):
print(f"[EMAIL] {symbol} gia: {price}")
class SMSAlert(PriceObserver):
def update(self, symbol: str, price: float):
print(f"[SMS] {symbol} gia: {price}")
# Usage
ticker = StockTicker()
ticker.attach(EmailAlert())
ticker.attach(SMSAlert())
ticker.set_price("AAPL", 175.50) # Cả Email và SMS đều nhận thông báo

Bảng so sánh: Factory Method vs Singleton vs Observer
| Tiêu chí | Factory Method | Singleton | Observer |
|---|---|---|---|
| Loại pattern | Creational | Creational | Behavioral |
| Mục đích chính | Tạo object mà không cần chỉ rõ class cụ thể | Đảm bảo chỉ một instance duy nhất | Thông báo thay đổi giữa object |
| Khả năng tái sử dụng | Cao | Trung bình (chỉ dùng khi cần 1 instance) | Rất cao (hệ thống event-driven) |
| Độ phức tạp | Trung bình | Thấp | Trung bình – Cao |
| Ví dụ thực tế | Payment gateway factory, Logger factory | DB connection pool, Config manager | Stock price alert, Event bus, Pub/Sub |
| Nguyên tắc SOLID liên quan | Open/Closed, Dependency Inversion | Single Responsibility | Open/Closed, Dependency Inversion |
Khi nào KHÔNG nên dùng Design Pattern?
Design Pattern là công cụ mạnh mẽ, nhưng không phải lúc nào cũng phù hợp. Việc áp dụng pattern không đúng có thể làm code phức tạp hơn mà không mang lại lợi ích thực tế:
- Over-engineering: Nếu bạn chỉ có 2-3 class, Factory Method có thể là overkill. Hãy bắt đầu với code đơn giản và refactor khi thực sự cần.
- Singleton làm tăng coupling: Singleton dễ bị lạm dụng và làm code khó test vì tạo ra hidden dependency. Trong microservices, dependency injection thường tốt hơn.
- Observer phức tạp với ít object: Nếu chỉ có 2 object cần thông báo nhau, Observer pattern thêm nhiều lớp code không cần thiết.
Best practices khi áp dụng Design Pattern
1. Hiểu vấn đề trước khi áp dụng pattern
Không chọn pattern vì nó “trendy”. Hãy xác định rõ vấn đề thiết kế bạn đang gặp phải, sau đó tìm pattern phù hợp. Một quy tắc tốt: nếu bạn không thể giải thích tại sao cần pattern đó trong 1 câu, có thể bạn không thực sự cần nó.
2. Kết hợp với Dependency Injection
Thay vì hard-code Singleton hay Factory, hãy inject dependency qua constructor. Điều này làm code dễ test hơn và giảm coupling:
# Tốt hơn hard-code
class OrderService:
def __init__(self, payment_factory: PaymentFactory):
self.payment_factory = payment_factory
def checkout(self, method: str, amount: float):
gateway = self.payment_factory.create_gateway(method)
return gateway.pay(amount)
3. Đặt tên rõ ràng
Nếu bạn dùng Factory Method, hãy đặt tên là create_xxx, build_xxx hoặc get_xxx để người đọc code hiểu ngay đây là factory. Tránh đặt tên chung chung như get_object.
4. Ghi chú lý do sử dụng pattern
Thêm comment ngắn gọn giải thích tại sao bạn dùng pattern này. Ví dụ: # Factory Method: cho phep them payment gateway moi ma khong sua checkout logic. Điều này giúp đồng nghiệp và chính bạn hiểu rõ hơn khi quay lại code sau vài tháng.
Kết luận
Factory Method, Singleton và Observer là ba trong số những design pattern phổ biến nhất mà mọi lập trình viên nên biết. Mỗi pattern giải quyết một nhóm vấn đề thiết kế cụ thể: Factory Method giúp tạo object linh hoạt, Singleton kiểm soát instance duy nhất, và Observer tạo cơ chế thông báo tự động giữa các object.
Quan trọng nhất là hiểu khi nào dùng pattern nào. Pattern không phải là công thức bắt buộc — mà là công cụ trong toolbox của bạn. Hãy bắt đầu với code đơn giản, nhận diện vấn đề thiết kế khi nó xuất hiện, sau đó refactor bằng pattern phù hợp. Chỉ có như vậy, design pattern mới thực sự mang lại giá trị thay vì trở thành overhead không cần thiết.
Nếu bạn muốn đi sâu hơn, hãy khám phá các pattern khác trong “Gang of Four” như Strategy, Decorator, Adapter và Facade — chúng cũng rất phổ biến trong các dự án backend và API hiện đại.
Xem thêm: Refactoring Guru — Design Patterns Catalog, GeeksforGeeks — Factory Method in Python, GeeksforGeeks — Singleton Pattern in Python
