Hướng dẫn Java Design Pattern – Observer

Chào mừng bạn đến với bài viết hôm nay! Trong lập trình hướng đối tượng, không thể không nhắc đến trạng thái của các đối tượng. Tất cả chúng ta đều biết rằng lập trình hướng đối tượng xoay quanh việc tạo ra các đối tượng và tương tác giữa chúng. Nhưng có những trường hợp, khi một số đối tượng cần phải được thông báo về những thay đổi diễn ra trên các đối tượng khác. Để có một thiết kế tốt, chúng ta cần tách rời các thành phần của ứng dụng và giảm độ phụ thuộc. Mẫu thiết kế Observer (quan sát) giúp chúng ta làm điều đó bằng cách cho phép các thành phần phụ thuộc tự động cập nhật khi có sự thay đổi trạng thái xảy ra.

Observer Pattern là gì?

Observer Pattern là một trong những mẫu thiết kế hành vi (Behavior Pattern) nổi tiếng. Nó thiết lập sự phụ thuộc một-nhiều (one-to-many) giữa các đối tượng, để khi một đối tượng thay đổi trạng thái, tất cả các thành phần phụ thuộc của nó sẽ được thông báo và cập nhật tự động.

Các đối tượng Observer có thể đăng ký với hệ thống và khi có sự thay đổi, hệ thống sẽ thông báo cho các Observer biết. Khi không cần thiết nữa, các Observer có thể bị gỡ bỏ khỏi hệ thống.

Trong ví dụ dưới đây, hệ thống đang giao tiếp với hai Observer: Observer 1 và Observer 2. Khi hệ thống phát sinh sự kiện cụ thể nào đó, nó sẽ thông báo (notification) với cả hai Observer.

Observer Pattern
Hình 1-10: Observer Pattern với 2 Observer

Observer Pattern còn được gọi là Dependents, Publish/Subscribe hoặc Source/Listener.

Cài đặt Observer Pattern như thế nào?

Để triển khai Observer Pattern, chúng ta cần những thành phần sau:

  • Subject: Chứa danh sách các Observer, cung cấp phương thức để thêm và loại bỏ Observer.
  • Observer: Định nghĩa một phương thức update() cho các đối tượng sẽ được thông báo khi có sự thay đổi trạng thái.
  • ConcreteSubject: Triển khai các phương thức của Subject, lưu trữ trạng thái và gửi thông báo đến các Observer của nó khi có sự thay đổi.
  • ConcreteObserver: Triển khai các phương thức của Observer, lưu trữ trạng thái của Subject và thực hiện việc cập nhật để giữ trạng thái đồng nhất với Subject.

Sự tương tác giữa Subject và các Observer diễn ra như sau: mỗi khi Subject thay đổi trạng thái, nó sẽ duyệt qua danh sách các Observer và gọi phương thức cập nhật trạng thái ở từng Observer. Có thể truyền chính Subject vào phương thức để Observer có thể lấy ra trạng thái của nó và xử lý.

Ví dụ Observer Pattern với ứng dụng Tracking thao tác một Account

Hãy tưởng tượng rằng chúng ta cần theo dõi tài khoản người dùng trong hệ thống. Mọi thao tác của người dùng đều cần được ghi log lại, chúng ta cần gửi mail thông báo khi tài khoản hết hạn và chặn người dùng nếu có truy cập không hợp lệ. Chúng ta có thể sử dụng Observer Pattern để giải quyết vấn đề này.

Dưới đây là một ví dụ về cách triển khai Observer Pattern trong ứng dụng theo dõi tài khoản người dùng:

public interface Subject {
    void attach(Observer observer);
    void detach(Observer observer);
    void notifyAllObserver();
}

public class AccountService implements Subject {
    private User user;
    private List observers = new ArrayList<>();

    public AccountService(String email, String ip) {
        user = new User();
        user.setEmail(email);
        user.setIp(ip);
    }

    @Override
    public void attach(Observer observer) {
        if (!observers.contains(observer))
            observers.add(observer);
    }

    @Override
    public void detach(Observer observer) {
        if (observers.contains(observer)) {
            observers.remove(observer);
        }
    }

    @Override
    public void notifyAllObserver() {
        for (Observer observer : observers) {
            observer.update(user);
        }
    }

    public void changeStatus(LoginStatus status) {
        user.setStatus(status);
        System.out.println("Status is changed");
        this.notifyAllObserver();
    }

    public void login() {
        if (!this.isValidIP()) {
            user.setStatus(LoginStatus.INVALID);
        } else if (this.isValidEmail()) {
            user.setStatus(LoginStatus.SUCCESS);
        } else {
            user.setStatus(LoginStatus.FAILURE);
        }
        System.out.println("Login is handled");
        this.notifyAllObserver();
    }

    private boolean isValidIP() {
        return "127.0.0.1".equals(user.getIp());
    }

    private boolean isValidEmail() {
        return "[email protected]".equalsIgnoreCase(user.getEmail());
    }
}

public interface Observer {
    void update(User user);
}

public class Logger implements Observer {
    @Override
    public void update(User user) {
        System.out.println("Logger: " + user);
    }
}

public class Mailer implements Observer {
    @Override
    public void update(User user) {
        if (user.getStatus() == LoginStatus.EXPIRED) {
            System.out.println("Mailer: User " + user.getEmail() + " is expired. An email was sent!");
        }
    }
}

public class Protector implements Observer {
    @Override
    public void update(User user) {
        if (user.getStatus() == LoginStatus.INVALID) {
            System.out.println("Protector: User " + user.getEmail() + " is invalid. " +
                "IP " + user.getIp() + " is blocked");
        }
    }
}

public class ObserverPatternExample {
    public static void main(String[] args) {
        AccountService account1 = createAccount("[email protected]", "127.0.0.1");
        account1.login();
        account1.changeStatus(LoginStatus.EXPIRED);

        System.out.println("-");

        AccountService account2 = createAccount("[email protected]", "116.108.77.231");
        account2.login();
    }

    private static AccountService createAccount(String email, String ip) {
        AccountService account = new AccountService(email, ip);
        account.attach(new Logger());
        account.attach(new Mailer());
        account.attach(new Protector());
        return account;
    }
}

Đầu ra của chương trình:

Login is handled
Logger: User([email protected], ip=127.0.0.1, status=SUCCESS)
Status is changed
Logger: User([email protected], ip=127.0.0.1, status=EXPIRED)
Mailer: User [email protected] is expired. An email was sent!
-
Login is handled
Logger: User([email protected], ip=116.108.77.231, status=INVALID)
Protector: User [email protected] is invalid. IP 116.108.77.231 is blocked

Lợi ích của Observer Pattern là gì?

Observer Pattern giúp chúng ta:

  • Dễ dàng mở rộng với ít sự thay đổi: Mẫu thiết kế này cho phép chúng ta thay đổi Subject và Observer một cách độc lập. Chúng ta có thể tái sử dụng Subject mà không cần tái sử dụng các Observer và ngược lại. Điều này giúp đảm bảo nguyên tắc Open/Closed Principle (OCP).
  • Phân tách mạnh mẽ đối tượng: Sự thay đổi trạng thái ở một đối tượng có thể được thông báo đến các đối tượng khác mà không cần giữ chúng liên kết quá chặt chẽ.
  • Tương tác một-nhiều: Một đối tượng có thể thông báo đến một số lượng không giới hạn các đối tượng khác.

Tuy nhiên, chúng ta cần lưu ý về trường hợp cập nhật không mong muốn (Unexpected update) của Subject. Bởi vì các Observer không biết về sự hiện diện của nhau, điều này có thể gây tốn nhiều chi phí khi thay đổi Subject.

Sử dụng Observer Pattern khi nào?

Observer Pattern thường được sử dụng trong các trường hợp sau:

  • Mối quan hệ một-nhiều: Thường được sử dụng khi một đối tượng thay đổi và muốn thông báo cho tất cả các đối tượng liên quan với nó về sự thay đổi đó.
  • Thay đổi nhiều đối tượng: Khi thay đổi một đối tượng yêu cầu thay đổi các đối tượng khác và chúng ta không biết chính xác có bao nhiêu đối tượng cần thay đổi và đó là những đối tượng nào.
  • Ứng dụng giao tiếp kiểu phát sóng (broadcast): Sử dụng để quản lý sự kiện (Event management).
  • Mô hình MVC (Model View Controller Pattern): Trong mô hình MVC, Observer Pattern được sử dụng để tách Model khỏi View. View là Observer và Model là Subject.

Hy vọng rằng bài viết này đã cung cấp cho bạn cái nhìn tổng quan về Observer Pattern và cách triển khai nó trong Java. Nếu bạn muốn tìm hiểu thêm, hãy tham khảo các tài liệu được đính kèm bên dưới.

Tài liệu tham khảo:

Nếu bạn có bất kỳ câu hỏi hoặc ý kiến nào, hãy để lại bình luận bên dưới. Chúng tôi rất mong muốn được nghe ý kiến của bạn!

FEATURED TOPIC