Mẫu Observer trong Java

1. Khái quát chung

Trong bài viết này, chúng ta sẽ mô tả mẫu Observer và xem xét một số lựa chọn thay thế triển khai Java.

2. Mô hình Người quan sát là gì?

Observer là một mẫu thiết kế hành vi. Nó chỉ định giao tiếp giữa các đối tượng: có thể quan sátquan sát được . Một quan sát được là một đối tượng mà thông báo cho các quan sát viên về những thay đổi trong trạng thái của nó.

Ví dụ, một hãng thông tấn có thể thông báo cho các kênh khi nhận được tin tức. Nhận tin tức là những gì thay đổi trạng thái của cơ quan tin tức và nó khiến các kênh được thông báo.

Hãy xem cách chúng tôi có thể tự thực hiện nó.

Đầu tiên, hãy xác định lớp NewsAgency :

public class NewsAgency { private String news; private List channels = new ArrayList(); public void addObserver(Channel channel) { this.channels.add(channel); } public void removeObserver(Channel channel) { this.channels.remove(channel); } public void setNews(String news) { this.news = news; for (Channel channel : this.channels) { channel.update(this.news); } } }

NewsAgency là một trang có thể quan sát được và khi tin tức được cập nhật, trạng thái của NewsAgency sẽ thay đổi. Khi thay đổi xảy ra, NewsAgency sẽ thông báo cho những người quan sát về thực tế này bằng cách gọi phương thức update () của họ .

Để có thể làm được điều đó, đối tượng có thể quan sát cần phải giữ các tham chiếu đến đối tượng quan sát và trong trường hợp của chúng ta, đó là biến kênh .

Hãy bây giờ nhìn thấy cách người quan sát , các kênh lớp, có thể như thế nào. Nó phải có phương thức update () được gọi khi trạng thái của NewsAgency thay đổi:

public class NewsChannel implements Channel { private String news; @Override public void update(Object news) { this.setNews((String) news); } }

Các kênh giao diện chỉ có một phương pháp:

public interface Channel { public void update(Object o); }

Bây giờ, nếu chúng ta thêm một thể hiện của NewsChannel vào danh sách các nhà quan sát , và thay đổi trạng thái của tiệm bán báo , trường hợp của NewsChannel sẽ được cập nhật:

NewsAgency observable = new NewsAgency(); NewsChannel observer = new NewsChannel(); observable.addObserver(observer); observable.setNews("news"); assertEquals(observer.getNews(), "news");

Có một giao diện Observer được xác định trước trong các thư viện lõi của Java, điều này làm cho việc triển khai mẫu quan sát trở nên đơn giản hơn. Hãy nhìn vào nó.

3. Thực hiện Với Người quan sát

Các java.util.Observer giao diện định nghĩa các bản cập nhật () phương pháp, do đó không cần phải định nghĩa nó mình là chúng tôi đã làm trong phần trước.

Hãy xem cách chúng ta có thể sử dụng nó trong quá trình triển khai của mình:

public class ONewsChannel implements Observer { private String news; @Override public void update(Observable o, Object news) { this.setNews((String) news); } } 

Ở đây, đối số thứ hai đến từ Observable như chúng ta sẽ thấy bên dưới.

Để định nghĩa lớp có thể quan sát , chúng ta cần mở rộng lớp Có thể quan sát của Java :

public class ONewsAgency extends Observable { private String news; public void setNews(String news) { this.news = news; setChanged(); notifyObservers(news); } }

Lưu ý rằng chúng ta không cần gọi phương thức update () của trình quan sát trực tiếp. Chúng tôi chỉ gọi setChanged () và thông báoObservers () , và lớp Observable sẽ làm phần còn lại cho chúng tôi.

Ngoài ra, nó còn chứa một danh sách những người quan sát và đưa ra các phương pháp để duy trì danh sách đó - addObserver ()deleteObserver ().

Để kiểm tra kết quả, chúng tôi chỉ cần thêm người quan sát vào danh sách này và đặt tin tức:

ONewsAgency observable = new ONewsAgency(); ONewsChannel observer = new ONewsChannel(); observable.addObserver(observer); observable.setNews("news"); assertEquals(observer.getNews(), "news");

Giao diện Observer không hoàn hảo và không được chấp nhận kể từ Java 9. Một trong những khuyết điểm của nó là Observable không phải là một giao diện mà là một lớp, đó là lý do tại sao các lớp con không thể được sử dụng như các lớp có thể quan sát được.

Ngoài ra, một nhà phát triển có thể ghi đè một số phương thức được đồng bộ hóa của Observable và phá vỡ sự an toàn luồng của chúng.

Hãy xem giao diện ProperyChangeListener , được khuyến nghị thay vì sử dụng Observer .

4. Thực hiện với PropertyChangeListener

Trong cách triển khai này, một đối tượng có thể quan sát phải giữ một tham chiếu đến cá thể PropertyChangeSupport . Nó giúp gửi thông báo cho người quan sát khi một thuộc tính của lớp bị thay đổi.

Hãy xác định những điều có thể quan sát được:

public class PCLNewsAgency { private String news; private PropertyChangeSupport support; public PCLNewsAgency() { support = new PropertyChangeSupport(this); } public void addPropertyChangeListener(PropertyChangeListener pcl) { support.addPropertyChangeListener(pcl); } public void removePropertyChangeListener(PropertyChangeListener pcl) { support.removePropertyChangeListener(pcl); } public void setNews(String value) { support.firePropertyChange("news", this.news, value); this.news = value; } }

Sử dụng hỗ trợ này , chúng tôi có thể thêm và xóa những người quan sát và thông báo cho họ khi trạng thái của những thay đổi có thể quan sát được:

support.firePropertyChange("news", this.news, value);

Ở đây, đối số đầu tiên là tên của thuộc tính được quan sát. Đối số thứ hai và thứ ba là giá trị cũ và mới của nó tương ứng.

Người quan sát nên triển khai PropertyChangeListener :

public class PCLNewsChannel implements PropertyChangeListener { private String news; public void propertyChange(PropertyChangeEvent evt) { this.setNews((String) evt.getNewValue()); } }

Do lớp PropertyChangeSupport đang thực hiện việc nối dây cho chúng ta, chúng ta có thể khôi phục giá trị thuộc tính mới từ sự kiện.

Hãy kiểm tra việc triển khai để đảm bảo rằng nó cũng hoạt động:

PCLNewsAgency observable = new PCLNewsAgency(); PCLNewsChannel observer = new PCLNewsChannel(); observable.addPropertyChangeListener(observer); observable.setNews("news"); assertEquals(observer.getNews(), "news");

5. Kết luận

Trong bài viết này, chúng tôi đã xem xét hai cách để triển khai mẫu thiết kế Observer trong Java, với cách tiếp cận PropertyChangeListener được ưu tiên hơn.

Mã nguồn của bài viết hiện có trên GitHub.