公號:碼農充電站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 等。
觀察者模式最經典的實現方式很簡單,在實際應用中,可以在其基礎上進行改進。
(本節完。)
推薦閱讀:
歡迎關注作者公眾號,獲取更多技術干貨。