Design Pattern phổ biến: Factory, Singleton, Observer áp dụng thực tế

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, SingletonObserver — 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.

Sơ đồ UML Factory Method Pattern với các lớp Creator, ConcreteCreator, Product và ConcreteProduct

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.

Sơ đồ UML Singleton Pattern với một lớp duy nhất và phương thức getInstance

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
Sơ đồ UML Observer Pattern với Subject và multiple Observers

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, AdapterFacade — 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

Tôi là một lập trình viên IOS. Code chính là IOS nhưng thỉnnh thoảng vẫn đá sang Android hoặc web. Mặc dù không quá thông thạo nhưng tôi sẽ chia sẻ những kiến thức mà mình đã tìm hiểu, áp dụng qua.

Bài viết liên quan

Event-Driven Architecture: Kafka, RabbitMQ, Apache Pulsar

Event-Driven Architecture là gì? Event-Driven Architecture (EDA) là kiến trúc phần mềm xây dựng xung quanh sự kiện — các thay đổi trạng thái trong hệ thống được phát hiện,…

Xem thêm

WebSocket thời gian thực trong web

WebSocket là gì? WebSocket là giao thức truyền thông hai chiều (full-duplex) qua một kết nối TCP duy nhất, cho phép server và client trao đổi dữ liệu thời gian…

Xem thêm
Polars DataFrame featured image comparison chart

Polars DataFrame: Thay thế Pandas cho xử lý dữ liệu lớn hiệu năng cao

Polars DataFrame là gì? Polars DataFrame là thư viện xử lý dữ liệu hiệu năng cao viết bằng Rust, được thiết kế như một sự thay thế nhanh hơn và…

Xem thêm
0 0 đánh giá
Article Rating
Theo dõi
Thông báo của
guest
0 Comments
Cũ nhất
Mới nhất Được bỏ phiếu nhiều nhất