設計模式-釋出訂閱模式(javaScript)
1. 前言
釋出訂閱者模式是為了釋出者和訂閱者之間避免產生依賴關係,釋出訂閱者之間的訂閱關係由一箇中介列表來維護。釋出者只需做好釋出功能,至於訂閱者是誰,訂閱者做了什麼事情,釋出者是無需關心的
2. 什麼是釋出訂閱模式
釋出訂閱 :是一種訊息正規化,訊息的傳送者(稱為釋出者)不會將訊息直接傳送給特定的接收者(稱為訂閱者)。而是將釋出的訊息分為不同的類別,無需瞭解哪些訂閱者(如果有的話)可能存在。同樣的,訂閱者可以表達對一個或多個類別的興趣,只接收感興趣的訊息,無需瞭解哪些釋出者(如果有的話)存在。(節選自百度百科)
就拿公眾號來說
- 只有該公眾號的訂閱者才能收到推送
- 公眾號只負責推送資訊,不關心是誰訂閱了我,只要有資訊推送,那麼就推送給所有的訂閱者
- 訂閱者無需時不時的檢視公眾號是否有資訊推送,只要公眾號推送資訊後,該訂閱者就會收到通知
- 訂閱者可隨時取消對該公眾號的訂閱
在呼叫方法時首先要釋出方法,確保呼叫方法能夠正常呼叫到。可以向一類相同事件中新增很多方法。當呼叫這一類方法時,可以統一呼叫整個流程。
釋出訂閱者模式,可以讓我們不再涉及更多的回撥處理,而且可以使模組的顆粒度更小。比如有個ajax的資料展示,其中一個訂閱者A可以只負責資料的表格展示,另一個訂閱者B只負責資料總量的計算。當有需求要把資料總量的計算修改為當前頁的資料總量和整體的資料總量計算,那麼訂閱者A是不用任何變動的!
3. 釋出訂閱優缺點
釋出訂閱模式確實為我們的程式碼帶來最小的耦合,並不是所有場景都適合使用這種模式,這種模式也有其利弊。
優點
- 支援簡單的廣播通訊,自動通知所有已經訂閱過的物件。
- 頁面載入後目標物件很容易與觀察者存在一種動態關聯,增加了靈活性。
- 目標物件與觀察者之間的抽象耦合關係能夠單獨擴充套件以及重用。
缺點
模組之間如果用了太多的全域性釋出-訂閱模式來通訊,那麼模組與模組之間的聯絡就被隱藏到了背後,我們最終會搞不清楚訊息來自哪個模組,或者訊息會流向哪些模組,這又會給我們的維護帶來一些麻煩,也許某個模組的作用就是暴露一些介面給其他模組呼叫。
4. 舉例
上面說了那麼多都是紙上談兵,那麼到底該訂閱釋出模式該如何實現呢?
簡單實現
class Boos { constructor(){ this.peopleList = {}; } add(key,fn){ let {peopleList} = this; !peopleList[key] && (peopleList[key] = []); this.peopleList[key].push(fn); } run(...arg){ let key = Array.prototype.shift.call(arg); let {peopleList} = this; let fns = peopleList[key]; if(!fns && !fns.length) return false; fns.forEach((el) => { el.apply(this,arg); }) } remove(key,fn){ let {peopleList} = this; let fns = peopleList[key]; if(!fns && !fns.length) return false; fns.forEach((el,index) => { if(el===fn) { fns.splice(index,1); } }) } } let boos = new Boos(); let married = (name) => { console.log(`${name}上班`); } let unemployment = (name) => { console.log(`${name}出差`); } boos.add('marrgie',married) boos.add('unemployment',unemployment) boos.run('marrgie','張三'); boos.remove('unemployment',unemployment); boos.run('unemployment','李四'); boos.run('marrgie','李四');
上面Boos
類,可以擁有釋出,執行和刪除任務,boos
給某個員工釋出命令,讓員工做他應該做的事情。無論是上班還是出差,我們不需要關係他們具體如何實現,只需要boos
知道員工能做什麼事情就好了。
優化
class Boos { constructor(){ this.peopleList = {}; } add(key,fn){ let {peopleList} = this; !peopleList[key] && (peopleList[key] = []); this.peopleList[key].push(fn); } run(...arg){ let key = Array.prototype.shift.call(arg); let {peopleList} = this; let fns = peopleList[key]; console.log(key) if(!fns && !fns.length) return false; fns.forEach((el) => { el.apply(this,arg); }) } remove(key,fn){ let {peopleList} = this; let fns = peopleList[key]; if(!fns && !fns.length) return false; fns.forEach((el,index) => { if(el===fn) { fns.splice(index,1); } }) } } class Work { married(name){ console.log(`${name}上班`); } unemployment(name){ console.log(`${name}出差`); } writing(name){ console.log(`${name}寫作`); } writeCode(name){ console.log(`${name}打程式碼`); } } class Staff { constructor(name){ this.name = name; } getName(){ return this.name; } } let boos = new Boos(); let work = new Work(); let aaron = new Staff("Aaron"); let angie = new Staff("Angie"); let aaronName = aaron.getName(); let angieName = angie.getName() boos.add(aaronName,work.married); boos.add(aaronName,work.writing); boos.add(aaronName,work.writeCode); boos.add(angieName,work.unemployment); boos.run(aaronName,aaronName); boos.run(angieName,angieName);
上面共維護了三個類,每個類都在做自己的事情,boos可以為員工分配不同的工作,即時需要新增工作類與類之間沒有任何溝耦合性。各自維護自己,任意一個類發生變化都不會互相影響。
總結
上面簡單的實現了一下發布訂閱者模式的模型,在樣例程式碼中,我們也能夠看到,釋出者和訂閱者之間僅僅依靠訂閱關係來維持,而且釋出者也不用關心訂閱者的內部具體是怎麼實現的。