設計模式之【觀察者模式】

Gopher大威發表於2022-04-04

表妹:哥啊,最近有個粉絲,老是動不動就噴。

:是不是你寫的文章不好呀?

表妹:才不是,也收到很多粉絲的鼓勵和點贊。

:這種網路噴子把他從粉絲列表中移除就好啦。

你看,這不就是我們設計模式中的觀察者模式嘛?該模式又稱為釋出-訂閱模式

定義物件間一種一對多的依賴關係,使得每當一個物件改變狀態,則所有依賴於它的物件都會得到通知並被自動更新。

它和生產-消費模型有什麼區別呢?

不同點:

觀察者模式(釋出-訂閱模型),是一對多的關係,可以以同步的方式實現,也可以以非同步的方式實現,訂閱者之間沒有競爭關係。

生產-消費模型,是多對多的關係,一般以非同步的方式實現,消費者之間存在競爭關係。

共同點:

兩者都可以達到解耦的作用。

觀察者模式

 

  • Observable被觀察者

    定義被觀察者必須實現的職責,它必須能夠動態地增加、取消觀察者。它一般是抽象類或者是實現類,僅僅完成作為被觀察者必須實現的職責:管理觀察者並通知觀察者。

  • Observer觀察者

    觀察者接收到訊息後,即進行update(更新方法)操作,對接收到的資訊進行處理。

  • ConcreteObservable具體的被觀察者

    定義被觀察者自己的業務邏輯,同時定義對哪些事件進行通知。

  • ConcreteObserver具體的觀察者

    每個觀察在接收到訊息後的處理反應是不同,各個觀察者有自己的處理邏輯。

我們熟悉的公共號就是一種觀察者模式。一個公眾號,會有多個粉絲物件來關注,當公眾號釋出推文的時候,所有關注的粉絲都會收到公眾號更新的通知,然後粉絲們會對通知做出相應的響應,執行相應的業務功能處理,比如閱讀文章,點贊文章,收藏文章等。

首先,定義觀察者和被觀察者兩個介面,方便擴充套件。

1 interface Observable {
2     void addObserver(Observer observer);
3     void removeObserver(Observer observer);
4     void notifyObservers(String message);
5 }
6 interface Observer { 7 void update(String name, String message); 8 }

ConcreteObservable:OfficialAccountOwner公眾號主,具體的被觀測者。

 1 class OfficialAccountOwner implements Observable {
 2     // 儲存已註冊的觀察者,當事件發生時,通知列表中的所有觀察者
 3     private List<Observer> fans;
 4     private String name;
 5     
 6     public OfficialAccountOwner(String name) {
 7         this.name = name;
 8         this.fans = new LinkedList<>();
 9     }
10     
11     // 釋出文章
12     public void sendMessage(String message) {
13         this.notifyObservers(message);
14     }
15     
16     @Override
17     public void addObserver(Observer observer) {
18         this.fans.add(observer);
19     }
20     
21     @Override 
22     public void removeObserver(Observer observer) {
23         this.fans.remove(observer);
24     }
25     
26     @Override
27     public void notifyObservers(String message) {
28         this.fans.forEach(fan-> {
29             fan.update(this.name, message);
30         });
31     }
32 }

ConcreteObserver:具體的觀察者,Fans1和Fans2。

 1 class Fans1 implements Observer {
 2     private String name;
 3     
 4     public Fans1(String name) {
 5         this.name = name;
 6     }
 7     
 8     @Override
 9     public void update(String name, String message) {
10         System.out.println(this.name + "閱讀" + name + "發的文章: " + message);
11     }
12 }
13 ​
14 class Fans2 implements Observer {
15     private String name;
16     
17     public Fans2(String name) {
18         this.name = name;
19     }
20     
21     @Override
22     public void update(String name, String message) {
23         System.out.println(this.name + "一鍵三連了" + name + "發的文章。");
24     }
25 }

接下來,我們來看一下實現效果。

 1 public class Demo {
 2     public static void main(String[] args) {
 3         OfficialAccountOwner DaWei = new OfficialAccountOwner("Gopher大威");
 4         Fans1 lisi = new Fans1("李四");
 5         Fans2 wangwu = new Fans2("王五");
 6         DaWei.addObserver(lisi);
 7         DaWei.addOvserver(wangwu);
 8         DaWei.sendMessage("設計模式之【觀察者模式】");
 9         // 李四這個粉絲,老是無緣無故噴,移除他
10         DaWei.removeObserver(lisi);
11         DaWei.sendMessage("設計模式之【釋出訂閱模式】");
12     }
13 }
14 ​
15 // 列印結果:
16 李四閱讀Gopher大威發的文章:設計模式之【觀察者模式】。
17 王五一鍵三連了Gopher大威發的文章。
18 // 粉絲李四被移除後
19 王五一鍵三連了Gopher大威發的文章。

你看,通過觀察者模式,將觀察者和被觀察者程式碼解耦。如果觀察者物件有新的更新邏輯的話,只需要新增一個實現了Observer介面的類,並通過addObserver()函式將它註冊到觀察者列表中即可,當被觀察者有事件響應的時候,就會通知到列表中的所有觀察者。

觀察者模式的優點

  • 觀察者和被觀察者之間是抽象耦合

    如此設計,則不管是增加觀察者還是被觀察者都非常容易擴充套件,而且在Java中都已經實現的抽象層級的定義,在系統擴充套件方面更是得心應手。

  • 建立一套觸發機制

觀察者模式的缺點

  • 如果一個被觀察者物件有很多的直接和間接的觀察者的話,將所有的觀察者都通知到會很花時間。

  • 如果觀察者和觀察目標間有迴圈依賴,可能導致系統崩潰。

  • 沒有相應的機制讓觀察者知道所觀察的目標物件是怎麼發生變化的。

觀察者模式的應用場景

  • 關聯行為場景。需要注意的是,關聯行為是可拆分的,而不是“組合”關係。

  • 事件多級觸發場景。

  • 跨系統的訊息交換場景,如訊息佇列的處理機制。

注意事項

採用非同步非阻塞方式,可以避免某一觀察者錯誤導致系統卡殼。實際場景可選擇訊息佇列,spring事件機制等。

觀察者模式在開原始碼中的應用

Spring中的事件驅動模型也叫釋出訂閱模式,是觀察者模式的一個典型應用。

事件機制的實現需要三個部分,事件源、事件、事件監聽器。

ApplicationEvent就相當於事件,ApplicationListener相當於事件監聽器,ApplicaitonContext就是事件源。

通過ApplicationEvent抽象類和ApplicationListener介面,可以實現ApplicationContext事件處理。監聽器在處理Event時,通常會進行判斷傳入的Event是不是自己所要處理的,使用instanceof關鍵字。

ApplicationEventMulticaster事件廣播器實現了監聽器的註冊,一般不需要我們實現,只需要顯示的呼叫applicationcontext.publisherEvent方法即可。

感興趣的同學,可以去看一下原始碼。

總結

觀察者模式的應用場景非常廣泛,小到程式碼層面的解耦,大到架構層面的系統解耦,再或者一些產品的設計思路,都有這種模式的影子,比如,郵件訂閱,RSS Feeds,本質上都是觀察者模式。不同的應用場景和需求下,這個模式也有截然不同的實現方式,有同步阻塞的實現方式,也有非同步非阻塞的實現方式;有程式內的實現方式,也有跨程式的實現方式。

參考

極客時間專欄《設計模式之美》

相關文章