Observer模式的概念
Observer模式是行為模式之一,它的作用是當一個對象的狀態發生變化時,能夠自動通知其他關聯對象,自動刷新對象狀態。
Observer模式提供給關聯對象一種同步通信的手段,使某個對象與依賴它的其他對象之間保持狀態同步。
Observer模式的角色
Subject(被觀察者)
被觀察的對象。當需要被觀察的狀態發生變化時,需要通知隊列中所有觀察者對象。Subject需要維持(添加,刪除,通知)一個觀察者對象的隊列列表。
ConcreteSubject
被觀察者的具體實現。包含一些基本的屬性狀態及其他操作。
Observer(觀察者)
接口或抽象類。當Subject的狀態發生變化時,Observer對象將通過一個callback函數得到通知。
ConcreteObserver
觀察者的具體實現:得到通知后將完成一些具體的業務邏輯處理。
觀察者模式( 又叫發布者-訂閱者模式 )應該是最常用的模式之一. 在很多語言里都得到大量應用. 包括我們平時接觸的dom事件. 也是js和dom之間實現的一種觀察者模式.
div.onclick = function click (){ alert ("click") }
只要訂閱了div的click事件. 當點擊div的時候, function click就會被觸發.
那么到底什么是觀察者模式呢. 先看看生活中的觀察者模式。
好萊塢有句名言. “不要給我打電話, 我會給你打電話”. 這句話就解釋了一個觀察者模式的來龍去脈。 其中“我”是發布者, “你”是訂閱者。
再舉個例子,我來公司面試的時候,完事之后每個面試官都會對我說:“請留下你的聯系方式, 有消息我們會通知你”。
在這里“我”是訂閱者, 面試官是發布者。所以我不用每天或者每小時都去詢問面試結果, 通訊的主動權掌握在了面試官手上。而我只需要提供一個聯系方式。
觀察者模式可以很好的實現2個模塊之間的解耦。 假如我正在一個團隊里開發一個html5游戲. 當游戲開始的時候,需要加載一些圖片素材。
加載好這些圖片之后開始才執行游戲邏輯. 假設這是一個需要多人合作的項目. 我完成了Gamer和Map模塊, 而我的同事A寫了一個圖片加載器loadImage.
loadImage的代碼如下
loadImage(imgAry, function () {
Map.init();
Gamer.init();
})
當圖片加載好之后, 再渲染地圖, 執行游戲邏輯. 嗯, 這個程序運行良好. 突然有一天, 我想起應該給游戲加上聲音功能. 我應該讓圖片加載器添上一行代碼.
loadImage(imgAry, function () {
Map.init();
Gamer.init();
Sount.init();
})
可是寫這個模塊的同事A去了外地旅游. 於是我打電話給他, 喂. 你的loadImage函數在哪, 我能不能改一下, 改了之后有沒有副作用.
如你所想, 各種不淡定的事發生了. 如果當初我們能這樣寫呢:
loadImage.listen("ready", function () { Map.init(); }) loadImage.listen("ready", function () { Gamer.init(); }) loadImage.listen("ready", function () { Sount.init(); })
loadImage完成之后, 它根本不關心將來會發生什么, 因為它的工作已經完成了. 接下來它只要發布一個信號
loadImage.trigger( "ready" );
下面是對觀察者模式的實現
/** * 發布訂閱模式(觀察者模式) * handles: 事件處理函數集合 * on: 訂閱事件 * emit: 發布事件 * off: 刪除事件 **/ class PubSub { constructor() { this.handles = {}; } // 訂閱事件 on(eventType, handle) { if (!this.handles.hasOwnProperty(eventType)) { this.handles[eventType] = []; } if (typeof handle == 'function') { this.handles[eventType].push(handle); } else { throw new Error('缺少回調函數'); } return this; } // 發布事件 emit(eventType, ...args) { if (this.handles.hasOwnProperty(eventType)) { this.handles[eventType].forEach((item, key, arr) => { item.apply(null, args); }) } else { throw new Error(`"${eventType}"事件未注冊`); } return this; } // 刪除事件 off(eventType, handle) { if (!this.handles.hasOwnProperty(eventType)) { throw new Error(`"${eventType}"事件未注冊`); } else if (typeof handle != 'function') { throw new Error('缺少回調函數'); } else { this.handles[eventType].forEach((item, key, arr) => { if (item == handle) { arr.splice(key, 1); } }) } return this; // 實現鏈式操作 } } // 下面做一些騷操作 let callback = function () { console.log('you are so nice'); } let pubsub = new PubSub(); pubsub.on('completed', (...args) => { console.log(args.join(' ')); }).on('completed', callback); pubsub.emit('completed', 'what', 'a', 'fucking day'); pubsub.off('completed', callback); pubsub.emit('completed', 'fucking', 'again');
輸出值:
what a fucking day
you are so nice
fucking again
