觀察者模式和發佈訂閱模式的概念在項目開發中很常見 ,這裏記錄一下自己的理解,詳解一下兩者的區別的並分別用代碼實現來直觀體現兩者區別,便於日後溫故知新
觀察者模式
觀察者模式別名也叫發佈-訂閱模式,但是發佈訂閱模式其實只是觀察者模式中的一種具體的實現方式
通常我們所說的觀察者和發佈訂閱模式的區別,其實只是把觀察者模式的一種原始的實現方式和現在的流行的發佈訂閱實現方式對比)。
好了,現在先在我們來理解一下原來的觀察者模式是如何實現的,試講訂閱者和發佈者直接關聯的,將多個訂閱者直接綁定到發佈者身上,然後當發佈者觸發事件的時候嗎通知各個訂閱者, 是一對多的關係。
這個我們可以舉個例子,這個實現方式就類似於原始的勞動市場,應聘者是訂閱者到勞動市場把簡歷投遞到一個個公司的郵箱裏,而公司相當與發佈者,當有一個hc時,公司就開始一個個聯繫各個應聘者,完成一次發佈。
圖解:(google找的)
代碼:
//觀察者模式
//招聘者
class Subject {
constructor() {
this.observers = [] //觀察者列表(應聘者列表)
}
addObserver(obj) { //添加觀察者(接受簡歷)
this.observers.push(obj)
}
notify(msg) { //通知觀察者 (通知應聘者來應聘)
this.observers.forEach((e)=> {
e.receiverNotice(msg)
})
}
}
//應聘者
class Observer {
constructor(params) {
this.name = params.name
}
receiverNotice(msg) {
//TODO 收到消息 趕緊去面試啊!!!
}
}
const ZhangSan = new Observer({name: '張三'})
const LiSi = new Observer({name: '李四'})
const company = new Subject()
//綁定觀察(投遞簡歷)
company.addObserver(ZhangSan)
company.addObserver(LiSi)
//發佈時間 (通知)
company.notify('快來面試啊!')
訂閱發佈模式
上文已經說了訂閱發佈模式其實只是觀察者模式的一種是實現方式, 是一種升級但是現在通常被當成一種單獨的設計模式。
我們可以將訂閱發佈模式抽象理解成現在的勞動市場,公司和應聘者不直接接觸,而由勞動市場做爲事件中心做轉發,應聘者向勞動市場登記自己的信息和技能,等到有公司向勞動市場發佈崗位的時候, 由勞動市場來一個個通知登記了的應聘者。這與上面的觀察者模式最大的區別就在於加一個事件中心(勞動中介)。
這樣公司就不需要在管理訂閱者,完全交由事件中心處理。 訂閱者和發佈者完全解耦,發佈者不需要關注怎麼綁定訂閱者,和如何發佈給訂閱者, 訂閱時不需要雙方都在場直接綁定 。這樣能大大讓代碼邏輯更爲清晰。同時可以將事件進行集中管理,可以進行統一的邏輯操作。
圖解:(來源google)
代碼
//event.js
class Events {
//事件池
pool = {}
on(eventKey, fn) {
if (!eventKey) return;
if (!fn || typeof fn !== 'function') {
return;
}
let eventPool = this.pool[eventKey] || [];
let flag = false;
if (eventPool.length) {
// 相同事件重複綁定
for (let i = 0; i < eventPool.length; i++) {
if (eventPool[i] === fn) {
flag = true;
break;
}
}
!flag ? eventPool.push(fn) : '';
} else {
eventPool.push(fn)
}
this.pool[eventKey] = eventPool;
}
//發佈時間
emit(eventKey) {
let p = Promise.resolve();
if (!eventKey) return;
let eventPool = this.pool[eventKey] || [], args;
if (eventPool.length) {
args = [].splice.call(arguments, 1);
eventPool.map(item => {
p = p.then(() => {
return itemFunc.apply(null, args);
}).catch(e => {
console.log(e)/
})
})
}
}
//解除事件綁定
off(eventKey, fn) {
let pool = this.pool;
// 解除所有事件
if (!eventKey) {
this.pool = {}
return;
}
// 解除所有eventKey事件
if (!fn || typeof fn !== 'function') {
delete pool[eventKey];
return;
}
if (!pool[eventKey]) {
return;
}
var mindex = pool[eventKey].findIndex((itemFunc => itemFunc === fn));
~mindex ? pool[eventkey].splice(mindex, 1) : '';
this.pool = pool;
}
}
const eventBus = new Events()
export default eventBus;
---------------------
// employee.js 註冊事件(登記信息)
import eventBus form "./event.js"
eventBus.on("應聘",()=> {
//TODO
})
---------------------
//compony.js 觸發時間(發佈一個崗位)
import eventBus form "./event.js"
eventBus.emit("應聘")
上面就是一個發佈訂閱設計模式實現的事件中心類,這個類可以直接在項目中使用,
首先這裏需要知道的是在各個文件中導入eventBus時, 只會導出同一個對象,event.js文件只會執行一次 ,一個項目中每次import的只是同一個對象的指針。(具體看一下es6中import的導入方式)
所以事件在各個文件中共享。這樣就可以保證多個文件進行事件交互。因爲訂閱事件的時候用的是箭頭函數,所以可以在外部修改作用域內的參數。這是很常用的開發場景。 類似多層的組件嵌套就可以不需要一層一層傳遞參數而用事件來觸發