觀察者模式-將訊息通知給觀察者
阿新 • • 發佈:2020-12-29
> **公號:碼農充電站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)