Return

Observer Pattern

Written by LocLT

Hãy tưởng tượng bạn đang xây dựng một tính năng mới cho ứng dụng của mình. Khi một sự kiện quan trọng xảy ra, ví dụ như “có người dùng mới đăng ký” hoặc “một đơn hàng được đặt thành công”, bạn cần thông báo cho nhiều bộ phận khác nhau trong hệ thống: gửi email chào mừng, gửi tin nhắn SMS xác nhận, và bắn một thông báo lên kênh Slack nội bộ.

Cách tiếp cận đơn giản nhất có thể là viết một hàm lớn chứa tất cả logic này. Nhưng điều gì sẽ xảy ra khi sếp của bạn yêu cầu “hãy thêm cả thông báo qua Telegram”? Bạn sẽ phải vào sửa lại hàm lớn đó, thêm một đoạn if-else hoặc một lời gọi hàm mới. Cứ như vậy, hàm của bạn sẽ ngày càng phình to, khó bảo trì và vi phạm nguyên tắc Mở/Đóng (Open/Closed Principle).

Đây chính là lúc Observer Pattern tỏa sáng. Trong bài viết này, chúng ta sẽ cùng tìm hiểu cách pattern này giải quyết vấn đề một cách thanh lịch qua ví dụ về NotificationSystemExercise mà chúng ta vừa thực hiện.

0. Observer Pattern Là Gì?

Observer Pattern định nghĩa một mối quan hệ “một-nhiều” (one-to-many) giữa các đối tượng. Khi một đối tượng (gọi là Subject ) thay đổi trạng thái, tất cả các đối tượng phụ thuộc nó (gọi là Observers ) sẽ được thông báo và tự động cập nhật.

Hãy nghĩ về ví dụ đời thực: bạn nhấn “Subscribe” (Đăng ký) một kênh YouTube. Ở đây:

  • Kênh YouTube là Subject .
  • Bạn là một Observer .

Khi kênh có video mới (thay đổi trạng thái), YouTube sẽ tự động gửi thông báo đến bạn và tất cả những người đăng ký khác. Kênh YouTube không cần biết bạn là ai, bạn dùng điện thoại gì, nó chỉ cần biết “ai đã đăng ký thì tôi thông báo”.

Bây giờ, hãy xem cách chúng ta đã áp dụng các khái niệm trên vào bài tập hệ thống thông báo.

1. Observer Interface (NotificationChannel)

Đầu tiên, chúng ta định nghĩa một hợp đồng chung cho tất cả các “người quan sát”. Bất kỳ ai muốn nhận thông báo đều phải tuân theo hợp đồng này.

// Observer Interface
interface NotificationChannel {
    void send(String message);
    void subscribe(EventManager eventManager);
    void unsubscribe(EventManager eventManager);
}

Interface này quy định rằng một kênh thông báo phải có khả năng send tin nhắn, và tự mình subscribe (đăng ký) hoặc unsubscribe (hủy đăng ký) với một EventManager.

2. Concrete Observers (Các Kênh Cụ Thể)

Đây là các lớp triển khai cụ thể của NotificationChannel. Mỗi lớp biết cách xử lý việc gửi thông báo theo cách riêng của nó.

// Concrete Observer 1: Kênh Email
class EmailChannel implements NotificationChannel {
    private String email;
    public EmailChannel(String email) { this.email = email; }
    
    @Override
    public void send(String message) {
        System.out.println("Sending EMAIL to " + email + ": " + message);
    }

    @Override
    public void subscribe(EventManager eventManager) {
        eventManager.addChannel(this);
    }
    // ... unsubscribe logic
}

// Concrete Observer 2: Kênh SMS
class SmsChannel implements NotificationChannel {
    private String phoneNumber;
    public SmsChannel(String phoneNumber) { this.phoneNumber = phoneNumber; }

    @Override
    public void send(String message) {
        System.out.println("Sending SMS to " + phoneNumber + ": " + message);
    }
    
    @Override
    public void subscribe(EventManager eventManager) {
        eventManager.addChannel(this);
    }
    // ... unsubscribe logic
}

3. Subject (EventManager)

Đây là trung tâm của hệ thống. Nó không biết gì về Email hay SMS, nó chỉ biết về NotificationChannel. Nhiệm vụ của nó là quản lý một danh sách các kênh đã đăng ký và thông báo cho tất cả khi có sự kiện.

// Subject (Observable)
class EventManager {
    private List<NotificationChannel> channels = new ArrayList<>();

    public void addChannel(NotificationChannel channel) {
        channels.add(channel);
    }

    public void removeChannel(NotificationChannel channel) {
        channels.remove(channel);
    }

    // Phương thức cốt lõi: thông báo cho tất cả observers
    public void notify(String message) {
        System.out.println("\n--- Notifying " + channels.size() + " channel(s)... ---");
        for (NotificationChannel channel : channels) {
            channel.send(message);
        }
    }
}

4. Kết Hợp Lại (Client Code)

Hàm main của chúng ta đóng vai trò là client, mô phỏng cách hệ thống hoạt động trong thực tế.

public static void main(String[] args) {
    // 1. Tạo Subject
    EventManager eventManager = new EventManager();

    // 2. Tạo các Observers
    NotificationChannel email = new EmailChannel("contact@gemini.com");
    NotificationChannel sms = new SmsChannel("098-765-4321");

    // 3. Các kênh tự đăng ký
    email.subscribe(eventManager);
    sms.subscribe(eventManager);

    // 4. Gửi thông báo -> Cả email và sms đều nhận được
    eventManager.notify("New product has been released!");

    // 5. Kênh sms hủy đăng ký
    sms.unsubscribe(eventManager);
    
    // 6. Gửi thông báo mới -> Chỉ email nhận được
    eventManager.notify("Limited stock remaining!");
}

Thiết kế này tốt vì sao?

  • Khớp nối lỏng lẻo (Loose Coupling): EventManager không hề biết đến sự tồn tại của EmailChannel hay SmsChannel. Nó chỉ làm việc với interface NotificationChannel. Điều này có nghĩa là bạn có thể thêm, bớt, thay đổi các kênh mà không ảnh hưởng gì đến EventManager.
  • Tuân thủ Nguyên tắc Mở/Đóng (Open/Closed Principle): Hệ thống của bạn mở cho việc mở rộng (bạn có thể tạo ra TelegramChannel hay PushNotificationChannel bất cứ lúc nào) nhưng lại đóng cho việc sửa đổi (bạn không cần phải đụng vào code của EventManager hay các kênh đã có để thêm kênh mới).

Kết Luận

Observer Pattern là một công cụ cực kỳ mạnh mẽ để xây dựng các hệ thống hướng sự kiện (event-driven). Nó giúp chúng ta tạo ra các thiết kế linh hoạt, dễ bảo trì và dễ mở rộng. Lần tới khi bạn gặp một bài toán yêu cầu “thông báo cho nhiều thành phần khi có sự kiện”, hãy nhớ đến Observer Pattern như một giải pháp hàng đầu.