Ý tưởng
Observer Pattern là một mẫu thiết kế hành vi cho phép bạn xác định một cơ chế đăng ký, cho phép nhiều đối tượng “quan sát” một đối tượng khác và được thông báo khi sự kiện xảy ra trên đối tượng đó.
Ứng dụng
- Sử dụng Observer Pattern khi việc thay đổi trạng thái của một đối tượng cần thay đổi các đối tượng khác, hoặc khi đối tượng thực tế là không xác định trước hoặc thay đổi động.
- Sử dụng Observer Pattern khi một số đối tượng trong ứng dụng phải quan sát các đối tượng khác. Tuy nhiên, việc quan sát chỉ diễn ra trong một khoảng thời gian hạn chế hoặc trong một số trường hợp cụ thể.
Cấu trúc
Giải thích cấu trúc
- Người phát hành (Publisher) gửi các sự kiện quan trọng đến các đối tượng khác. Sự kiện xảy ra khi trạng thái của người phát hành thay đổi hoặc khi một hành động cụ thể được thực hiện. Người phát hành chứa một cơ chế đăng ký cho phép các người đăng ký mới tham gia và các người đăng ký hiện tại rời khỏi danh sách.
- Khi một sự kiện mới xảy ra, người gửi sẽ lặp qua danh sách đăng ký và gọi phương thức thông báo của mỗi đối tượng đăng ký. Phương thức này được khai báo trong giao diện đăng ký.
- Người đăng ký (Subscriber) giao diện khai báo phương thức thông báo. Trong hầu hết các trường hợp, giao diện này chỉ chứa một phương thức cập nhật (update). Phương thức này có thể có nhiều tham số, cho phép người phát hành truyền thông tin chi tiết về sự kiện trong quá trình cập nhật.
- Người đăng ký cụ thể (Concrete Subscribers) có thể thực hiện một số hành động để phản hồi thông báo từ người phát hành. Tất cả các lớp người đăng ký cụ thể đều triển khai cùng một giao diện, do đó người phát hành không cần phụ thuộc vào các lớp cụ thể.
- Người đăng ký thường cần một số thông tin ngữ cảnh để xử lý cập nhật một cách chính xác. Do đó, người phát hành thường truyền một số dữ liệu ngữ cảnh như tham số cho phương thức thông báo. Người phát hành cũng có thể truyền chính nó như một tham số để cho người đăng ký truy cập trực tiếp vào dữ liệu cần thiết.
- Khách hàng (Client) tạo các đối tượng người phát hành và người đăng ký, sau đó đăng ký người đăng ký với người phát hành để nhận thông báo từ nó.
Mẫu code cấu trúc
Subject: Là lớp chủ đề, lưu trữ tất cả các quan sát viên đăng ký cho chủ đề này, số lượng quan sát viên là tùy ý. Xác định giao diện để thêm quan sát viên (Attach) và xóa quan sát viên (Detach).
Observer: Là lớp quan sát viên, xác định giao diện cập nhật (Update), khi nhận được thông báo từ Chủ đề, Quan sát viên cần cập nhật thông tin đồng bộ.
ConcreteSubject: Là lớp chủ đề cụ thể, lưu trữ tất cả các quan sát viên quan tâm đến chủ đề này. Khi trạng thái nội bộ thay đổi, nên thông báo cho tất cả các quan sát viên đã đăng ký (Notify).
ConcreteObserver: Là lớp quan sát viên cụ thể, triển khai phương thức cập nhật (Update) của Observer để đồng bộ thông tin trạng thái với Chủ đề.
Client
Output
======= Chủ đề Thời tiết có thông báo mới =======
John nhận được trạng thái hiện tại: Hôm nay mưa
Alice nhận được trạng thái hiện tại: Hôm nay mưa
Bob nhận được trạng thái hiện tại: Hôm nay mưa
======= Chủ đề Thời tiết có thông báo mới =======
John nhận được trạng thái hiện tại: Ngày mai nắng
Bob nhận được trạng thái hiện tại: Ngày mai nắng
Pseudocode
Trong ví dụ này, mẫu Quan sát viên cho phép đối tượng trình soạn thảo văn bản thông báo về các thay đổi trạng thái của nó cho các đối tượng dịch vụ khác.
Danh sách người đăng ký được tạo động: đối tượng có thể bắt đầu hoặc dừng lắng nghe thông báo theo nhu cầu của chương trình.
Trong cài đặt này, lớp trình soạn thảo không tự quản lý danh sách đăng ký. Nó giao việc này cho một đối tượng trợ giúp đặc biệt chịu trách nhiệm. Bạn cũng có thể nâng cấp đối tượng này thành một trình phân phối sự kiện trung tâm, cho phép bất kỳ đối tượng nào trở thành người phát hành.
Miễn là người phát hành tương tác với tất cả các người đăng ký thông qua cùng một giao diện, không cần sửa đổi mã của lớp người phát hành hiện có khi thêm người đăng ký vào chương trình.
Mối quan hệ với các mẫu khác
- Chain of Responsibility Pattern, Command Pattern, Mediator Pattern và Observer Pattern được sử dụng để xử lý các cách kết nối khác nhau giữa người gửi yêu cầu và người nhận:
- Chain of Responsibility chuyển tiếp yêu cầu động theo thứ tự cho một loạt các người nhận tiềm năng cho đến khi một người nhận xử lý yêu cầu.
- Command thiết lập một kết nối một chiều giữa người gửi và người nhận yêu cầu.
- Mediator loại bỏ kết nối trực tiếp giữa người gửi và người nhận, buộc họ giao tiếp gián tiếp thông qua một đối tượng trung gian.
- Observer cho phép người nhận đăng ký hoặc hủy đăng ký động để nhận yêu cầu.
- Sự khác biệt giữa Mediator Pattern và Observer Pattern thường khó nhớ. Trong hầu hết các trường hợp, bạn có thể sử dụng một trong hai mẫu này, và đôi khi bạn có thể sử dụng cả hai cùng một lúc. Hãy xem cách làm điều đó.
- Mục tiêu chính của Mediator là loại bỏ sự phụ thuộc lẫn nhau giữa các thành phần của hệ thống. Các thành phần này sẽ phụ thuộc vào cùng một đối tượng trung gian. Mục tiêu của Observer là thiết lập kết nối động một chiều giữa các đối tượng, cho phép một số đối tượng phụ thuộc vào các đối tượng khác để thực hiện chức năng phụ.
- Có một cách phổ biến để triển khai Mediator dựa trên Observer. Đối tượng trung gian đóng vai trò như một người phát hành, trong khi các thành phần khác đóng vai trò như các người nghe, có thể đăng ký hoặc hủy đăng ký với người phát hành. Khi Mediator được triển khai theo cách này, nó có thể trông giống như Observer rất nhiều.
- Khi bạn cảm thấy bối rối, hãy nhớ rằng Mediator có thể được triển khai theo cách khác. Ví dụ, bạn có thể liên kết tất cả các thành phần với cùng một đối tượng trung gian một cách vĩnh viễn. Cách triển khai này không giống với Observer, nhưng nó vẫn là một mẫu Mediator.
- Giả sử có một chương trình trong đó tất cả các thành phần trở thành người phát hành và có thể thiết lập kết nối động với nhau. Trong trường hợp đó, không có một đối tượng trung tâm trung gian, mà chỉ có một số người nghe phân tán.
Ví dụ
Sử dụng ví dụ: Mẫu Observer rất phổ biến trong mã Java, đặc biệt là trong các thành phần GUI. Nó cung cấp một cách để phản ứng với các sự kiện của thành phần mà không phụ thuộc vào lớp chủ sở hữu khác.
Dưới đây là một số ví dụ trong thư viện cốt lõi của Java:
java.util.Observer
/java.util.Observable
(rất ít được sử dụng trong thực tế)- Tất cả các triển khai của
java.util.EventListener
(rất phổ biến trong các thành phần Swing) javax.servlet.http.HttpSessionBindingListener
javax.servlet.http.HttpSessionAttributeListener
javax.faces.event.PhaseListener
Cách nhận biết: Mẫu này có thể được nhận biết bằng cách lưu trữ đối tượng trong một danh sách đăng ký và gọi phương thức cập nhật cho các đối tượng trong danh sách khi có sự cập nhật từ phía đối tượng gửi yêu cầu.