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

碼農充電站發表於2020-12-29

公號:碼農充電站pro
主頁:https://codeshellme.github.io

觀察者模式Observer Design Pattern)也被稱為釋出訂閱模式Publish-Subscribe Design Pattern),主要用於更好的解決向物件通知訊息的問題。

觀察者模式定義了物件之間的一對多依賴,當物件狀態改變的時候,所有依賴者都會自動收到通知。

觀察者模式可以用很多稱呼來描述,比如:

  • Subject-Observer:主題-觀察者。
  • Publisher-Subscriber:釋出者-訂閱者。
  • Producer-Consumer:生產者-消費者。

1,訂閱報紙

我們以訂閱報紙為例,來描述 SubjectObserver 之間的關係。

Subject 相當於報社Observer 就相當於訂閱報紙的使用者

  • 從報社的角度來說:
    • 報社可向使用者提供新聞訊息,使用者可以訂閱報社的報紙,也可以取消訂閱。
    • 報社記錄了所有訂閱報紙的使用者名稱單
      • 如果有使用者訂閱了報紙,報社就會在名單中加入他的名字。
      • 如果有使用者取消了報紙,報社就會從名單中刪去他的名字。
    • 當報社有了新聞,會主動將新聞通知給它的所有訂閱使用者。
    • 沒有訂閱的使用者,報社則不會去通知他。
  • 從使用者的角度來說:
    • 如果使用者訂閱了報社的報紙,當報社有了新聞,他就會收到報社的訊息。
    • 如果使用者取消了報社的報紙,當報社有了新聞,報社也不會通知他。

SubjectObserver一對多關係

在這裡插入圖片描述

2,觀察者模式類圖

這裡直接給出觀察者模式的類圖,這是最經典的實現方式,其它的變種都可以在它的基礎上加以改進。

在這裡插入圖片描述

從類圖中可以知道,Subject 是一個介面,有三個抽象方法:

  • registerObserver:用於註冊 observer
  • removeObserver:用於移除 observer
  • notifyObservers:當有了訊息,通知所有 observers

Observer 也是一個介面,有一個抽象方法:

  • update:當 subject 發來新訊息時,用於更新訊息。

3,實現觀察者模式

下面我們來用程式碼實現觀察者模式。

首先是兩個介面 SubjectObserver

interface Subject {
    void registerObserver(Observer o);
    void removeObserver(Observer o);
    void notifyObservers(String info);
}

interface Observer {
    void update(String info);
}

這兩個介面完全是按照類圖中的內容來實現的,其中變數 info 的型別可以根據實際的應用場景來定。

再實現 ConcreteSubjectConcreteObserver

class ConcreteSubject implements Subject {

	// 用於存放 observer
    private final ArrayList<Observer> 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 只是為了表示不同的觀察者,觀察者在收到訊息後,將訊息列印在控制檯。

測試這兩個類:

// 建立被觀察者
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"); // 再向觀察者通知訊息

輸出如下:

o1 get info: info1
o2 get info: info1
remove observer o1
o2 get info: info2

可以看到,第一次通知訊息時,o1o2 都收到訊息了,在移除 o1 之後再傳送訊息,只有 o2 能收到訊息。

這就是觀察者模式最簡潔的一種實現方式,非常簡單。我把完整的程式碼放在了這裡

4,觀察者模式擴充套件

根據不同的應用場景和需求,觀察者模式可以有不同的實現方式,比如下面幾種:

  • 同步阻塞的實現方式
  • 非同步非阻塞的實現方式
  • 程式內的實現方式
  • 跨程式的實現方式

同步阻塞方式

根據這種劃分方式,上面我們實現的就是同步阻塞的方式,當有新訊息的時候,Subject 會將訊息 notify 給所有的 Observer,直到所有的 Observer 執行完畢它的 update 過程,整個通知過程才算完畢,這整個過程是一個阻塞的過程。

非同步非阻塞方式

為了加快整個 notify 過程,我們可以將同步阻塞的方式改為非同步非阻塞的方式。

一種簡單的實現就是使用執行緒,就是在 update 方法中使用執行緒來完成任務,如下:

public void update(String info) {
   Thread t = new Thread(new Runnable() {
       public void run() {
           // 處理任務
       }
   });

   t.start();
}

Google Guava EventBusExplained 是一個通用的觀察者模式框架,你可以去了解一下。

跨程式方式

同步阻塞非同步非阻塞都屬於程式之內的實現,對於跨程式的實現,一般都是基於訊息佇列來實現。至於這方面的應用,有很多現成的,成熟的元件可以使用,比如:

  • Redis Pub/Sub:一個快速、穩定的釋出/訂閱訊息傳遞系統。
  • ActiveMQ:一個基於 Java 的多協議的訊息傳遞服務。
  • RocketMQ:一個訊息傳遞引擎。
  • Kafka:一個分散式的大資料流處理平臺。

5,總結

觀察者模式旨在將觀察者與被觀察者解耦,在很多地方都用到了該模式,比如 SwingJavaBeans 等。

觀察者模式最經典的實現方式很簡單,在實際應用中,可以在其基礎上進行改進。

(本節完。)


推薦閱讀:

設計模式之高質量程式碼

單例模式-讓一個類只有一個物件

工廠模式-將物件的建立封裝起來

策略模式-定義一個演算法族


歡迎關注作者公眾號,獲取更多技術乾貨。

碼農充電站pro

相關文章