1. 程式人生 > >「補課」進行時:設計模式(15)——觀察者模式

「補課」進行時:設計模式(15)——觀察者模式

![](https://cdn.geekdigging.com/DesignPatterns/java_design_pattern.jpg) ## 1. 前文彙總 [「補課」進行時:設計模式系列](https://www.geekdigging.com/category/%e8%ae%be%e8%ae%a1%e6%a8%a1%e5%bc%8f/) ## 2. 觀察者模式 ### 2.1 定義 觀察者模式(Observer Pattern)也叫做釋出訂閱模式(Publish/subscribe),它是一個在專案中經常使用的模式,其定義如下: Define a one-to-many dependency between objects so that when oneobject changes state,all its dependents are notified and updatedautomatically.(定義物件間一種一對多的依賴關係,使得每當一個物件改變狀態,則所有依賴於它的物件都會得到通知並被自動更新。) ### 2.2 通用類圖 ![](https://cdn.geekdigging.com/DesignPatterns/15/Observer_UML.png) - Subject 被觀察者: 定義被觀察者必須實現的職責,它必須能夠動態地增加、取消觀察者。它一般是抽象類或者是實現類,僅僅完成作為被觀察者必須實現的職責:管理觀察者並通知觀察者。 - ConcreteSubject 具體的被觀察者: 定義被觀察者自己的業務邏輯,同時定義對哪些事件進行通知。 - Observer 觀察者: 觀察者接收到訊息後,即進行update(更新方法)操作,對接收到的資訊進行處理。 - ConcreteObserver 具體的觀察者: 每個觀察在接收到訊息後的處理反應是不同,各個觀察者有自己的處理邏輯。 ### 2.3 通用程式碼 **Subject 被觀察者:** ```java public abstract class Subject { // 定義一個觀察者陣列 private Vector obsVector = new Vector<>(); // 新增一個觀察者 public void addObserver(Observer obsVector) { this.obsVector.add(obsVector); } // 刪除一個觀察者 public void delObserver(Observer observer) { this.obsVector.remove(observer); } // 通知所有觀察者 public void notifyObservers() { for (Observer obs : this.obsVector) { obs.update(); } } } ``` **ConcreteSubject 具體的被觀察者:** ```java public class ConcreteSubject extends Subject { public void doSomething() { // 具體的業務 super.notifyObservers(); } } ``` **Observer 觀察者:** ```java public interface Observer { void update(); } ``` **ConcreteObserver 具體的觀察者:** ```java public class ConcreteObserver implements Observer{ @Override public void update() { System.out.println("進行訊息處理"); } } ``` **測試場景類:** ```java public class Test { public static void main(String[] args) { // 建立一個被觀察者 ConcreteSubject subject = new ConcreteSubject(); // 建立一個觀察者 Observer observer = new ConcreteObserver(); // 觀察者觀察被觀察者 subject.addObserver(observer); // 觀察者開始活動了 subject.doSomething(); } } ``` ## 3. 一個案例 觀察者模式是設計模式中的**超級模式**,有關他的應用隨處可見。 就比如說微信公眾號,我每天推送一篇博文內容,訂閱的使用者都能夠在我釋出推送之後及時接收到推送,方便地在手機端進行閱讀。 **訂閱者介面(觀察者)** ```java public interface Subscriber { void receive(String publisher, String articleName); } ``` **微信客戶端(具體觀察者)** ```java public class WeChatClient implements Subscriber { private String username; public WeChatClient(String username) { this.username = username; } @Override public void receive(String publisher, String articleName) { System.out.println(String.format("使用者<%s> 接收到 <%s>微信公眾號 的推送,文章標題為 <%s>", username, publisher, articleName)); } } ``` **一個微信客戶端(具體觀察者)** ```java public class Publisher { private List subscribers; private boolean pubStatus = false; public Publisher() { subscribers = new ArrayList<>(); } protected void subscribe(Subscriber subscriber) { this.subscribers.add(subscriber); } protected void unsubscribe(Subscriber subscriber) { if (this.subscribers.contains(subscriber)) { this.subscribers.remove(subscriber); } } protected void notifySubscribers(String publisher, String articleName) { if (this.pubStatus == false) { return; } for (Subscriber subscriber : this.subscribers) { subscriber.receive(publisher, articleName); } this.clearPubStatus(); } protected void setPubStatus() { this.pubStatus = true; } protected void clearPubStatus() { this.pubStatus = false; } } ``` **具體目標** ```java public class WeChatAccounts extends Publisher { private String name; public WeChatAccounts(String name) { this.name = name; } public void publishArticles(String articleName, String content) { System.out.println(String.format("\n<%s>微信公眾號 釋出了一篇推送,文章名稱為 <%s>,內容為 <%s> ", this.name, articleName, content)); setPubStatus(); notifySubscribers(this.name, articleName); } } ``` **測試類** ```java public class Test { public static void main(String[] args) { WeChatAccounts accounts = new WeChatAccounts("極客挖掘機"); WeChatClient user1 = new WeChatClient("張三"); WeChatClient user2 = new WeChatClient("李四"); WeChatClient user3 = new WeChatClient("王五"); accounts.subscribe(user1); accounts.subscribe(user2); accounts.subscribe(user3); accounts.publishArticles("設計模式 | 觀察者模式及典型應用", "觀察者模式的內容..."); accounts.unsubscribe(user1); accounts.publishArticles("設計模式 | 單例模式及典型應用", "單例模式的內容...."); } } ``` **測試結果** ```java <極客挖掘機>微信公眾號 釋出了一篇推送,文章名稱為 <設計模式 | 觀察者模式及典型應用>,內容為 <觀察者模式的內容...> 使用者<張三> 接收到 <極客挖掘機>微信公眾號 的推送,文章標題為 <設計模式 | 觀察者模式及典型應用> 使用者<李四> 接收到 <極客挖掘機>微信公眾號 的推送,文章標題為 <設計模式 | 觀察者模式及典型應用> 使用者<王五> 接收到 <極客挖掘機>微信公眾號 的推送,文章標題為 <設計模式 | 觀察者模式及典型應用> <極客挖掘機>微信公眾號 釋出了一篇推送,文章名稱為 <設計模式 | 單例模式及典型應用>,內容為 <單例模式的內容....> 使用者<李四> 接收到 <極客挖掘機>微信公眾號 的推送,文章標題為 <設計模式 | 單例模式及典型應用> 使用者<王五> 接收到 <極客挖掘機>微信公眾號 的推送,文章標題為 <設計模式 | 單例模式及典型應用> ``` ## 4. JDK 對的觀察者模式的支援 觀察者模式在 Java 語言中的地位非常重要。在 JDK 的 `java.util` 包中,提供了 `Observable` 類以及 `Observer` 介面,它們構成了JDK對觀察者模式的支援。 在 `java.util.Observer` 介面中,僅有一個 `update(Observable o, Object arg)` 方法,當觀察目標發生變化時被呼叫: ```java public interface Observer { void update(Observable o, Object arg); } ``` `java.util.Observable` 類則為目標類: ```java public class Observable { private boolean changed = false; private Vector obs; public Observable() { obs = new Vector<>(); } // 用於註冊新的觀察者物件到向量中 public synchronized void addObserver(Observer o) { if (o == null) throw new NullPointerException(); if (!obs.contains(o)) { obs.addElement(o); } } // 用於刪除向量中的某一個觀察者物件 public synchronized void deleteObserver(Observer o) { obs.removeElement(o); } public void notifyObservers() { notifyObservers(null); } // 通知方法,用於在方法內部迴圈呼叫向量中每一個觀察者的update()方法 public void notifyObservers(Object arg) { Object[] arrLocal; synchronized (this) { if (!changed) return; arrLocal = obs.toArray(); clearChanged(); } for (int i = arrLocal.length-1; i>=0; i--) ((Observer)arrLocal[i]).update(this, arg); } // 用於清空向量,即刪除向量中所有觀察者物件 public synchronized void deleteObservers() { obs.removeAllElements(); } // 該方法被呼叫後會設定一個boolean型別的內部標記變數changed的值為true,表示觀察目標物件的狀態發生了變化 protected synchronized void setChanged() { changed = true; } // 用於將changed變數的值設為false,表示物件狀態不再發生改變或者已經通知了所有的觀察者物件,呼叫了它們的update()方法 protected synchronized void clearChanged() { changed = false; } // 返回物件狀態是否改變 public synchronized boolean hasChanged() { return changed; } // 返回向量中觀察者的數量 public synchronized int countObservers() { return obs.size(); } } ``` 相比較我們自己的示例 `Publisher` , `java.util.Observer` 中多了併發和 NPE 方面的考慮 。 使用 JDK 對觀察者模式的支援,改寫一下上面的示例: 增加一個通知類 `WechatNotice` ,用於推送通知的傳遞: ```java public class WechatNotice { private String publisher; private String articleName; // 省略 get/set } ``` 然後改寫 `WeChatClient` 和 `WeChatAccounts` ,分別實現 JDK 的 `Observer` 介面和繼承 `Observable` 類: ```java public class WeChatClient implements Observer { private String username; public WeChatClient(String username) { this.username = username; } @Override public void update(Observable o, Object arg) { WechatNotice notice = (WechatNotice) arg; System.out.println(String.format("使用者<%s> 接收到 <%s>微信公眾號 的推送,文章標題為 <%s>", username, notice.getPublisher(), notice.getArticleName())); } } public class WeChatAccounts extends Observable { private String name; public WeChatAccounts(String name) { this.name = name; } public void publishArticles(String articleName, String content) { System.out.println(String.format("\n<%s>微信公眾號 釋出了一篇推送,文章名稱為 <%s>,內容為 <%s> ", this.name, articleName, content)); setChanged(); notifyObservers(new WechatNotice(this.name, articleName)); } } ``` 最後是一個測試類: ```java public class Test { public static void main(String[] args) { WeChatAccounts accounts = new WeChatAccounts("極客挖掘機"); WeChatClient user1 = new WeChatClient("張三"); WeChatClient user2 = new WeChatClient("李四"); WeChatClient user3 = new WeChatClient("王五"); accounts.addObserver(user1); accounts.addObserver(user2); accounts.addObserver(user3); accounts.publishArticles("設計模式 | 觀察者模式及典型應用", "觀察者模式的內容..."); accounts.deleteObserver(user1); accounts.publishArticles("設計模式 | 單例模式及典型應用", "單例模式的內容...."); } } ``` 測試結果: ```java <極客挖掘機>微信公眾號 釋出了一篇推送,文章名稱為 <設計模式 | 觀察者模式及典型應用>,內容為 <觀察者模式的內容...> 使用者<王五> 接收到 <極客挖掘機>微信公眾號 的推送,文章標題為 <設計模式 | 觀察者模式及典型應用> 使用者<李四> 接收到 <極客挖掘機>微信公眾號 的推送,文章標題為 <設計模式 | 觀察者模式及典型應用> 使用者<張三> 接收到 <極客挖掘機>微信公眾號 的推送,文章標題為 <設計模式 | 觀察者模式及典型應用> <極客挖掘機>微信公眾號 釋出了一篇推送,文章名稱為 <設計模式 | 單例模式及典型應用>,內容為 <單例模式的內容....> 使用者<王五> 接收到 <極客挖掘機>微信公眾號 的推送,文章標題為 <設計模式 | 單例模式及典型應用> 使用者<李四> 接收到 <極客挖掘機>微信公眾號 的推送,文章標題為 <設計模式 | 單例模式及典型應用> ``` 和前面的示例結果完全