什麼是觀察者模式?
一個或多個觀察者對目標的狀態感興趣,它們通過將自己依附在目標對象上以便註冊所感興趣的內容。
目標狀態發生改變並且觀察者可能對這些改變感興趣,就會發送一個通知消息,調用每個觀察這的更新方法。
當觀察者不再對目標狀態感興趣時,他們可以簡單地將自己從中分離。
看完後你會覺得這是什麼破玩意?不要急,我們舉個現實中的例子,來按段落順序逐步分解下上面這段話的意思。
去飯館吃飯,點完餐會給你一個號碼,然後就和其餘點完餐的人一樣,坐着等叫號。
每當做好一份餐,服務員就會喊多少號的餐好了。然後每個人都會收到這個消息,並開始檢查自己手裏的號,看是不是自己的餐好了,如果是自己的餐,就不要坐着了,趕緊去吃飯了。
有些人等着等着,覺得不想吃了,可能是嫌做飯太慢氣飽了,就離開不吃了。
抽象組件
我們把上面這個場景再簡化抽象成 JS 描述。
比如,一個 JS 對象 O 被修改了,那麼它就需要自動通知那些依賴它的對象。這裏所有依賴對象 O 的對象都是觀察者,而對象 O 就是一個目標。
根據這個圖,我們就可以假設對象 O 都是通過目標類(Subject Class)實例化出來的,而依賴對象 O 的那些對象都是通過觀察者類(Observer Class)實例化出來的。
那麼我們就可以抽象出來 4 個組建:
-
目標(Subject)
維護一系列的觀察者,方便添加或刪除觀察者。對應一個目標類,提供一些註冊和通知觀察者的接口。 -
觀察者(Observer)
在目標狀態發生改變時,爲需要得到通知的對象提供一個更新接口。對應一個觀察者類,提供一個更新觀察者狀態的接口。 -
具體目標(ConcreteSubject)
狀態發生改變時,向 Observer 發出通知,存儲 ConcreteObserver 的狀態。對應一個目標實例。 -
具體觀察者(ConcreteObserver)
存儲一個指向 ConcreteSubject 的引用,實現 Observer 類的更新接口,以是自身狀態與目標狀態保持一致。對應一個觀察者實例。
實現
1、爲了管理觀察者,我們先實現一個 ObserverList 類。也就是一個數組結構,並提供一些常用的數組操作接口。
class ObserverList {
constructor() {
this.observerList = []
}
add(obj) {
return this.observerList.push(obj)
}
get(index) {
if (index > -1 && index < this.observerList.length) {
return this.observerList[index]
}
}
count() {
return this.observerList.length
}
remove(index) {
this.observerList.splice(index, 1)
}
indexOf(obj) {
return this.observerList.indexOf(obj)
}
}
2、目標類。提供註冊和通知觀察者等接口。
class Subject {
constructor() {
this.observers = new ObserverList()
}
addObserver(observer) {
this.observers.add(observer)
}
removeObserver(observer) {
let index = this.observers.indexOf(observer)
if (index >= 0) {
this.observers.remove(index)
}
}
notify() {
const observerCount = this.observers.count()
for (let i = 0; i < observerCount; i++) {
this.observers.get(i).update(context)
}
}
}
3、Observer 類。提供一個觀察者更新接口。
class Observer {
update() {
// ...
}
}
以上便是觀察者模式的實現方式。
使用
下面我們來看下觀察模式的使用,我們繼續前面飯館吃飯的場景。
<button id="callClient">Call</button>
// 先實例化一個飯館
const restaurant = new Subject()
// 實例化兩個顧客 a 和 b
const a = new Observer()
const b = new Observer()
// 定義當前被叫號的顧客
let currentObserver
/**
* @desc a 顧客實現自己的更新方法
* @params restaurant 飯堂實例
*/
a.update = function(restaurant) {
// 判斷當前的叫號是不是自己
if (restaurant.observers.get(0) == a) {
console.log('我是a,我的飯好了')
// 如果是自己,把當前叫號的顧客賦值成自己
currentObserver = a
}
}
// a 顧客實現自己的更新方法
b.update = function(restaurant) {
// 判斷當前的叫號是不是自己
if (restaurant.observers.get(0) == b) {
console.log('我是b,我的飯好了')
// 如果是自己,把當前叫號的顧客賦值成自己
currentObserver = b
}
}
// 兩位顧客先後在飯堂點餐,把自己註冊爲觀察者
restaurant.addObserver(a)
restaurant.addObserver(b)
// 綁定通知,每次點擊通知所有顧客,餐好了
callClient.onclick = function() {
// 飯好了,通知所有顧客
restaurant.notify(restaurant)
// 把拿走飯的顧客,移除,不需要關心了
restaurant.removeObserver(currentObserver)
}
以上就是一個模擬場景,應用了觀察者模式。如果有不正確的地方,望指出。