觀察者模式
當物件之間存在一對多的依賴關係時,當被觀察的物件狀態發生改變時,所有觀察它的物件都會收到通知,這就是觀察者模式。
基本思想
在觀察者模式中,只有兩種主體:目標物件 (Subject
) 和 觀察者 (Observer
)。
在觀察者模式中,Subject 物件擁有新增、刪除和通知一系列 Observer 的方法等,而 Observer 物件擁有 update 更新方法等。在 Subject 物件新增了一系列 Observer 物件之後,Subject 物件則維持著這一系列 Observer 物件,當有關狀態發生變更時 Subject 物件則會通知這一系列 Observer 物件進行更新。
優點
- 耦合度高,通常用來實現一些響應式的效果;
- 角色很明確,沒有事件排程中心作為中間者,目標物件
Subject
和觀察者Observer
都要實現約定的成員方法; - 雙方聯絡緊密,目標物件的主動性很強,自己收集和維護觀察者,並在狀態變化時主動通知觀察者更新;
實現
// 目標物件
class Subject {
constructor() {
this.observers = []
}
add (observer) {
this.observers.push(observer)
}
notify() {
this.observers.map(observer => {
if (observer && typeof observer.update === 'function') {
observer.update()
}
})
}
remove(observer) {
const idx = this.observers.findIndex(itm => itm === observer)
if (idx !== -1) {
this.observers.splice(idx, 1)
}
}
}
// 觀察者
class Observer {
constructor(name) {
this.name = name
}
update() {
console.log(`${this.name} updated`)
}
}
const subject = new Subject()
const o1 = new Observer('Nina')
const o2 = new Observer('Jack')
subject.add(o1)
subject.add(o2)
console.log('第一次通知:')
subject.notify()
subject.remove(o1)
console.log('刪除 Nina 後,再次通知:')
subject.notify()
輸出為:
釋出-訂閱模式
基於一個事件(主題)通道,希望接收通知的物件 Subscriber
通過自定義事件訂閱主題,被啟用事件的物件 Publisher
通過釋出主題事件的方式通知各個訂閱該主題的 Subscriber
物件,這就是釋出訂閱模式。
釋出訂閱模式中有三個角色:釋出者 Publisher
,事件排程中心 Event Channel
,訂閱者 Subscriber
。
特點
- 釋出訂閱模式中,對於釋出者
Publisher
和訂閱者Subscriber
沒有特殊的約束,他們藉助事件排程中心提供的介面釋出和訂閱事件,互不瞭解對方是誰; - 鬆散耦合,靈活度高,常用作事件匯流排;
- 易理解,可類比於
DOM
事件中的dispatchEvent
和addEventListener
;
缺點
- 當事件型別越來越多時,難以維護,需要考慮事件命名的規範,也要防範資料流混亂。
實現
class EventEmitter {
constructor() {
this.list = {}
if (!EventEmitter.instance) {
EventEmitter.instance = this
}
return EventEmitter.instance
}
on(name, fn) {
if (!this.list[name]) {
this.list[name] = []
}
this.list[name].push(fn)
}
emit(...rest) {
const name = ([].shift.call(rest))
const fns = this.list[name] || []
fns.forEach((fn) => {
fn.apply(this, rest)
})
}
off(name) {
this.list[name] = []
}
clean() {
this.list = {}
}
}
const e1 = new EventEmitter()
e1.on('go', (name) => console.log(`${name} 走了`))
e1.on('come', (name) => console.log(`${name} 來了`))
e1.emit('go', 'Nina')
e1.emit('go', 'Jack')
e1.emit('come', 'Bill')
二者區別
概念與實現上
- 從概念上理解,兩者沒什麼不同,都在解決物件之間解耦,通過事件的方式在某個時間點進行觸發,監聽這個事件的訂閱者可以進行相應的操作。
- 在實現上有所不同,觀察者模式對訂閱事件的訂閱者通過釋出者自身來維護,後續的一些列操作都要通過釋出者完成。釋出訂閱模式是訂閱者和釋出者中間會有一個事件匯流排,操作都要經過事件匯流排完成。
耦合
- 觀察者模式是面向目標和觀察者程式設計的,用於耦合目標和觀察者。觀察者和被觀察者之間還存在耦合,被觀察者還是知道觀察者的;
- 釋出 - 訂閱模式是面向排程中心程式設計的,用於解耦釋出者和訂閱者。釋出者和訂閱者不需要知道對方的存在,通過訊息代理進行通訊,解耦更加徹底;
關係
- 觀察者模式的觀察者和被觀察者就像 商家-顧客 的關係,當商品有更新等,商家會直接通知訂閱的顧客。
- 釋出訂閱模式的釋出者和訂閱者,就像 商家-APP-顧客 的關係,顧客(訂閱者)在APP上訂閱商品通知,待商品有更新時,商家(釋出者)通過APP通知訂閱的顧客(訂閱者)。
從使用層面上講
- 觀察者模式,多用於單個應用內部;
- 釋出訂閱模式,更多的是一種跨應用的模式(cross-application pattern),比如訊息中介軟體;