發布/訂閱模式和觀察者模式一樣嗎?
在許多地方我們都能見到基于這二者或者說基于某種設計模式的框架,函式或插件
在瀏覽器中使用addEventListener(type,fn)對dom元素進行事件委托,事件監聽用戶的異步操作
Android中也有一個事件發布/訂閱的輕量級框架:EventBus,原理與web相似
Socket.io的許多方法也是基于此類模式,監聽與觸發事件,批量廣播等
在Node中同樣也有一個events事件觸發器解決異步操作的同步回應
那么其二者有什么區別嗎,下面兩張圖可以簡單描述他們的程序(發布者/訂閱者模式我直接用事件的偵聽(addeventlistener)與事件的派遣(dispatchevent)來形容幫助理解)

Observer Pattern(觀察者模式):
Subject(主題,或者叫被觀察者):當狀態發送變化時,需要通知佇列中關聯物件
Observer(觀察者):當Subject發送訊息時,通過回呼獲得資訊
Observer(觀察者)將事件(記做fn回呼)丟給Subject(被觀察者),然后就開始監視著他的一舉一動,當Subject(被觀察者)的(異步)任務完成后,同步觸發事件fn回呼將訊息傳輸給Observer(觀察者)后,完成一個完整的周期
實作程序:
Observer.js:
module.exports = class Observer { //定義觀察者類,每個實體化后的觀察者擁有訂閱(subscribe)功能
constructor() {}
/**
* 訂閱
* @param target 被觀察者Subject的實體物件
* @param fn 訂閱注冊的回呼
*/
subscribe(target, fn) {
target.observerList.push(fn)
}
}
Subject.js:
module.exports = class Subject { //定義被觀察者類,每個實體化后擁有注冊的觀察者回呼的串列(observerList)和觸發回呼(fireEvent)功能
constructor() {
this.observerList = []
}
/**
* 觸發
* @param e 被觀察者傳遞給觀察者的引數
*/
fireEvent(e) {
this.observerList.forEach(item => {
item(e)
})
}
}
main.js(使用場景)
const Observer = require('./js/observer');
const Subject = require('./js/subject')
class MyObserver extends Observer {}
class MySubject extends Subject {}
// 實體化兩個觀察者,同時對一個subject進行監聽
const observer = new MyObserver()
const observer2 = new MyObserver()
const subject = new MySubject()
observer.subscribe(subject, (e) => {
console.log(e) //hello world
})
observer2.subscribe(subject, (e) => {
console.log(e) //hello world
})
// 延時激活觀察者注冊的函式,傳遞引數
setTimeout(subject.fireEvent.bind(subject, 'hello world'), 1000)
Publisher-Subscriber Pattern(發布者/訂閱者模式):
Subscriber(訂閱者):將事件注冊到事件調度中心(Event Channel或者可以看做EventBus(事件總線))
Publisher(發布者):觸發調度中心的事件
Event Channel(調度中心),與Vue和Android中的EventBus(事件總線)相似:得到Publisher(發布者)的訊息后,統一處理Subscriber(訂閱者)注冊的事件
Subscriber(訂閱者)通過on將事件注冊到Event Channel(調度中心),并與Event Channel通過回呼進行資料傳遞,當Subscriber(訂閱者)觸發Event Channel(調度中心)的事件并將資料傳遞至其中時,調度中心會激活之前與Subscriber(訂閱者)建立的聯系,通過emit發送資料,訂閱者收到資料后完成一個周期
實作程序:
eventBus.js
// 發布/訂閱設計模式(Pub/Sub)
class EventBus {
constructor() {
this._eventList = {} //調度中心串列
}
static Instance() { //回傳當前類的實體的單例
if (!EventBus._instance) {
Object.defineProperty(EventBus, "_instance", {
value: new EventBus()
});
}
return EventBus._instance;
}
/**
* 注冊事件至調度中心
* @param type 事件型別,特指具體事件名
* @param fn 事件注冊的回呼
*/
onEvent(type, fn) { //訂閱者
if (!this.isKeyInObj(this._eventList, type)) { //若調度中心未找到該事件的佇列,則新建某個事件串列(可以對某個型別的事件注冊多個回呼函式)
Object.defineProperty(this._eventList, type, {
value: [],
writable: true,
enumerable: true,
configurable: true
})
}
this._eventList[type].push(fn)
}
/**
* 觸發調度中心的某個或者某些該事件型別下注冊的函式
* @param type 事件型別,特指具體事件名
* @param data 發布者傳遞的引數
*/
emitEvent(type, data) { //發布者
if (this.isKeyInObj(this._eventList, type)) {
for (let i = 0; i < this._eventList[type].length; i++) {
this._eventList[type][i] && this._eventList[type][i](data)
}
}
}
offEvent(type, fn) { //銷毀監聽
for (let i = 0; i < this._eventList[type].length; i++) {
if (this._eventList[type][i] && this._eventList[type][i] === fn) {
this._eventList[type][i] = null
}
}
}
/**
* 檢查物件是否包含該屬性,除原型鏈
* @param obj 被檢查物件
* @param key 被檢查物件的屬性
*/
isKeyInObj(obj, key) {
if (Object.hasOwnProperty.call(obj, key)) {
return true
}
return false
}
}
module.exports = EventBus.Instance()
main.js
const EventBus = require('./js/eventBus')
let list = [], //記錄異步操作
count = 0, //計數器
timeTick = setInterval(function () {
if (count++ > 3) { //當執行到一定時間時,銷毀事件、定時器
EventBus.offEvent('finish', eventHandler)
clearInterval(timeTick)
}
list.push(count)
EventBus.emitEvent('finish', {
list
})
}, 1000)
EventBus.onEvent('finish', eventHandler)
function eventHandler(e) {
console.log(e)
// { list: [ 1 ] }
// { list: [ 1, 2 ] }
// { list: [ 1, 2, 3 ] }
// { list: [ 1, 2, 3, 4 ] }
}
總結:發布者/訂閱者模式實際上是基于觀察者模式上優化實作的,然而其二者的區別還是有的
觀察者模式:定義物件間的一種一對多的依賴關系,當一個物件的狀態發生改變時,所有依賴于它的物件都得到通知并被自動更新
優點:觀察者和被觀察者是抽象耦合的,其二者建立了一套觸發機制,松耦合
缺點:二者之間回圈依賴,如果關系復雜,如觀察者數量過多,還是會造成性能問題,解決方式是避免同步執行造成執行緒阻塞
發布者/訂閱者模式:與觀察者模式類似,但是核心區別是發布者與訂閱者互相無耦合,并不知道通知與被通知的對方的具體身份,而是將注冊的函式放在統一的調度中心進行管理
優點:發布者/訂閱者完全解耦,可擴展性高,常應用在分布式,緊耦合服務中
缺點:發布者解耦訂閱者,這點既是主要優點,亦是缺點,打個比方,在Socket中,倘若服務端發送訊息給客戶端,不會在意是否發送成功,此時需要客戶端回傳接收到了訊息才能算是保證了代碼的可靠性和可用性
相關原始碼:碼云地址
轉載請註明出處,本文鏈接:https://www.uj5u.com/qianduan/258975.html
標籤:其他
上一篇:前端MUI+H5+HBuilderX開發APP(IOS,android),后臺Springboot,首頁,圖片輪播等,更新中(三 )
