1. 程式人生 > >觀察者模式-將訊息通知給觀察者

觀察者模式-將訊息通知給觀察者

> **公號:碼農充電站pro** > **主頁:** **觀察者模式**(*Observer Design Pattern*)也被稱為**釋出訂閱模式**(*Publish-Subscribe Design Pattern*),主要用於更好的解決向**物件**通知訊息的問題。 **觀察者模式**定義了物件之間的一對多依賴,當物件狀態改變的時候,所有依賴者都會自動收到通知。 觀察者模式可以用很多稱呼來描述,比如: - **Subject-Observer**:主題-觀察者。 - **Publisher-Subscriber**:釋出者-訂閱者。 - **Producer-Consumer**:生產者-消費者。 ### 1,訂閱報紙 我們以訂閱報紙為例,來描述 **Subject** 與 **Observer** 之間的關係。 **Subject** 相當於**報社**,**Observer** 就相當於訂閱報紙的**使用者**: - 從報社的角度來說: - 報社可向使用者提供新聞訊息,使用者可以**訂閱**報社的報紙,也可以**取消**訂閱。 - 報社記錄了所有訂閱報紙的**使用者名稱單**。 - 如果有使用者訂閱了報紙,報社就會在名單中**加入**他的名字。 - 如果有使用者取消了報紙,報社就會從名單中**刪去**他的名字。 - 當報社有了新聞,會主動將新聞**通知**給它的所有訂閱使用者。 - 沒有訂閱的使用者,報社則不會去通知他。 - 從使用者的角度來說: - 如果使用者**訂閱**了報社的報紙,當報社有了新聞,他就會**收到**報社的訊息。 - 如果使用者**取消**了報社的報紙,當報社有了新聞,報社也不會通知他。 **Subject** 與 **Observer** 是**一對多關係**: ![在這裡插入圖片描述](https://img-blog.csdnimg.cn/20201225223154349.png?) ### 2,觀察者模式類圖 這裡直接給出觀察者模式的類圖,這是最經典的實現方式,其它的變種都可以在它的基礎上加以改進。 ![在這裡插入圖片描述](https://img-blog.csdnimg.cn/20201225231053785.png?) 從類圖中可以知道,**Subject** 是一個介面,有三個抽象方法: - `registerObserver`:用於註冊 **observer**。 - `removeObserver`:用於移除 **observer**。 - `notifyObservers`:當有了訊息,通知所有 **observers**。 **Observer** 也是一個介面,有一個抽象方法: - `update`:當 **subject** 發來新訊息時,用於更新訊息。 ### 3,實現觀察者模式 下面我們來用程式碼實現觀察者模式。 首先是兩個介面 **Subject** 與 **Observer**: ```java interface Subject { void registerObserver(Observer o); void removeObserver(Observer o); void notifyObservers(String info); } interface Observer { void update(String info); } ``` 這兩個介面完全是按照類圖中的內容來實現的,其中變數 `info` 的型別可以根據實際的應用場景來定。 再實現 **ConcreteSubject** 和 **ConcreteObserver** : ```java class ConcreteSubject implements Subject { // 用於存放 observer private final ArrayList observers; public ConcreteSubject() { observers = new ArrayList(); } public void registerObserver(Observer o) { observers.add(o); } public void removeObserver(Observer o) { observers.remove(o); } public void notifyObservers(String info) { for (Observer o: observers) { o.update(info); } } } class ConcreteObserver implements Observer { private final String name; public ConcreteObserver(String name) { this.name = name; } public void update(String info) { System.out.println(this.name + " get info: " + info); } } ``` `ConcreteSubject ` 中的 `observers` 用於儲存觀察者,這裡使用的型別是 `ArrayList`,也可以根據實際的應用場景來選擇。 `ConcreteObserver ` 中的 `name` 只是為了表示不同的觀察者,觀察者在收到訊息後,將訊息列印在控制檯。 測試這兩個類: ```java // 建立被觀察者 ConcreteSubject s = new ConcreteSubject(); // 建立兩個觀察者 ConcreteObserver o1 = new ConcreteObserver("o1"); ConcreteObserver o2 = new ConcreteObserver("o2"); // 註冊觀察者 s.registerObserver(o1); // 註冊 o1 s.registerObserver(o2); // 註冊 o2 s.notifyObservers("info1"); // 向觀察者通知訊息 System.out.println("remove observer o1"); s.removeObserver(o1); // 移除 o1 s.notifyObservers("info2"); // 再向觀察者通知訊息 ``` 輸出如下: ```shell o1 get info: info1 o2 get info: info1 remove observer o1 o2 get info: info2 ``` 可以看到,第一次通知訊息時,**o1** 和 **o2** 都收到訊息了,在移除 **o1** 之後再發送訊息,只有 **o2** 能收到訊息。 這就是觀察者模式最簡潔的一種實現方式,非常簡單。我把完整的程式碼放在了[這裡](https://github.com/codeshellme/codeshellme.github.io/blob/master/somecode/dp/Observers.java)。 ### 4,觀察者模式擴充套件 根據不同的應用場景和需求,觀察者模式可以有不同的實現方式,比如下面幾種: - **同步阻塞**的實現方式 - **非同步非阻塞**的實現方式 - **程序內**的實現方式 - **跨程序**的實現方式 ***同步阻塞方式*** 根據這種劃分方式,上面我們實現的就是**同步阻塞**的方式,當有新訊息的時候,`Subject` 會將訊息 `notify` 給所有的 `Observer`,直到所有的 `Observer` 執行完畢它的 `update` 過程,整個通知過程才算完畢,這整個過程是一個**阻塞**的過程。 ***非同步非阻塞方式*** 為了加快整個 `notify` 過程,我們可以將**同步阻塞**的方式改為**非同步非阻塞**的方式。 一種簡單的實現就是使用**執行緒**,就是在 `update` 方法中使用執行緒來完成任務,如下: ```java public void update(String info) { Thread t = new Thread(new Runnable() { public void run() { // 處理任務 } }); t.start(); } ``` [Google Guava EventBusExplained](https://github.com/google/guava/wiki/EventBusExplained) 是一個通用的觀察者模式框架,你可以去了解一下。 ***跨程序方式*** **同步阻塞**與**非同步非阻塞**都屬於程序之內的實現,對於跨程序的實現,一般都是基於**訊息佇列**來實現。至於這方面的應用,有很多現成的,成熟的元件可以使用,比如: - [Redis Pub/Sub](https://redis.io/topics/pubsub):一個快速、穩定的**釋出/訂閱**訊息傳遞系統。 - [ActiveMQ](https://activemq.apache.org/):一個基於 **Java** 的多協議的訊息傳遞服務。 - [RocketMQ](http://rocketmq.apache.org/):一個訊息傳遞引擎。 - [Kafka](http://kafka.apache.org/):一個分散式的大資料流處理平臺。 ### 5,總結 觀察者模式旨在將觀察者與被觀察者解耦,在很多地方都用到了該模式,比如 **Swing**,**JavaBeans** 等。 觀察者模式最經典的實現方式很簡單,在實際應用中,可以在其基礎上進行改進。 (本節完。) --- **推薦閱讀:** [設計模式之高質量程式碼](https://www.cnblogs.com/codeshell/p/13968620.html) [單例模式-讓一個類只有一個物件](https://www.cnblogs.com/codeshell/p/14177102.html) [工廠模式-將物件的建立封裝起來](https://www.cnblogs.com/codeshell/p/14187677.html) [策略模式-定義一個演算法族](https://www.cnblogs.com/codeshell/p/14200531.html) --- *歡迎關注作者公眾號,獲取更多技術乾貨。* ![碼農充電站pro](https://img-blog.csdnimg.cn/20200505082843773.png?#pic_center)