公號:碼農充電站pro
主頁:https://codeshellme.github.io
觀察者模式(Observer Design Pattern)也被稱為釋出訂閱模式(Publish-Subscribe Design Pattern),主要用於更好的解決向物件通知訊息的問題。
觀察者模式定義了物件之間的一對多依賴,當物件狀態改變的時候,所有依賴者都會自動收到通知。
觀察者模式可以用很多稱呼來描述,比如:
- Subject-Observer:主題-觀察者。
- Publisher-Subscriber:釋出者-訂閱者。
- Producer-Consumer:生產者-消費者。
1,訂閱報紙
我們以訂閱報紙為例,來描述 Subject 與 Observer 之間的關係。
Subject 相當於報社,Observer 就相當於訂閱報紙的使用者:
- 從報社的角度來說:
- 報社可向使用者提供新聞訊息,使用者可以訂閱報社的報紙,也可以取消訂閱。
- 報社記錄了所有訂閱報紙的使用者名稱單。
- 如果有使用者訂閱了報紙,報社就會在名單中加入他的名字。
- 如果有使用者取消了報紙,報社就會從名單中刪去他的名字。
- 當報社有了新聞,會主動將新聞通知給它的所有訂閱使用者。
- 沒有訂閱的使用者,報社則不會去通知他。
- 從使用者的角度來說:
- 如果使用者訂閱了報社的報紙,當報社有了新聞,他就會收到報社的訊息。
- 如果使用者取消了報社的報紙,當報社有了新聞,報社也不會通知他。
Subject 與 Observer 是一對多關係:
2,觀察者模式類圖
這裡直接給出觀察者模式的類圖,這是最經典的實現方式,其它的變種都可以在它的基礎上加以改進。
從類圖中可以知道,Subject 是一個介面,有三個抽象方法:
registerObserver
:用於註冊 observer。removeObserver
:用於移除 observer。notifyObservers
:當有了訊息,通知所有 observers。
Observer 也是一個介面,有一個抽象方法:
update
:當 subject 發來新訊息時,用於更新訊息。
3,實現觀察者模式
下面我們來用程式碼實現觀察者模式。
首先是兩個介面 Subject 與 Observer:
interface Subject {
void registerObserver(Observer o);
void removeObserver(Observer o);
void notifyObservers(String info);
}
interface Observer {
void update(String info);
}
這兩個介面完全是按照類圖中的內容來實現的,其中變數 info
的型別可以根據實際的應用場景來定。
再實現 ConcreteSubject 和 ConcreteObserver :
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
可以看到,第一次通知訊息時,o1 和 o2 都收到訊息了,在移除 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,總結
觀察者模式旨在將觀察者與被觀察者解耦,在很多地方都用到了該模式,比如 Swing,JavaBeans 等。
觀察者模式最經典的實現方式很簡單,在實際應用中,可以在其基礎上進行改進。
(本節完。)
推薦閱讀:
歡迎關注作者公眾號,獲取更多技術乾貨。