觀察者模式於釋出訂閱模式的關係
觀察者模式於釋出訂閱模式的關係
在《JavaScript設計模式》中,對觀察者模式的定義:又被稱作釋出-訂閱者模式或訊息機制,定義了一中依賴關係,解決了主題於觀察者之間功能的耦合。
這裡的定義將觀察者和釋出-訂閱者,兩種模式稱作同一中,但是實際上觀察者模式和釋出訂閱模式還是有一些區別的。
下面是一段書中作為觀察者模式的程式碼:
NoDatavar Observer = (function () { let _messages = {} return { regist: function(type, fn) { if (typeof _messages[type] === 'undefined') { _messages[type] = [fn] }else { _messages[type].push(fn) } }, fire: function(type, args) { if (!_messages[type]) { return } let events = { type: type, args: args || {} } for (let i = 0; i < _messages[type].length; i++) { _messages[type][i].call(this, events) } }, remove: function(type, fn) { if (_messages[type] instanceof Array) { for (let i = _messages[type].length - 1; i >= 0; i--) { _messages[type][i] === fn && _messages[type].splice(i, 1) } } } } })() // 訂閱者 // 學生類 const Student = function(result) { let that = this that.result = result that.say = function() { console.log(that.result) } } Student.prototype.answer = function(question) { Observer.regist(question, this.say) } Student.prototype.sleep = function(question) { console.log(this.result + ':' + question + '已被登出') Observer.remove(question, this.say) } // 釋出者 // 教師類 const Teacher = function() {} Teacher.prototype.ask = function(question) { console.log('問題是:' + question) Observer.fire(question) } const student1 = new Student('學生1回答問題') const student2 = new Student('學生2回答問題') const student3 = new Student('學生3回答問題') student1.answer('什麼是設計模式') student1.answer('什麼是觀察者模式') student2.answer('什麼是設計模式') student3.answer('什麼是設計模式') student3.answer('什麼是觀察者模式') const teacher = new Teacher() teacher.ask('什麼是設計模式') student3.sleep('什麼是觀察者模式') teacher.ask('什麼是觀察者模式') //問題是:什麼是設計模式 //學生1回答問題 //學生2回答問題 //學生3回答問題 //學生3回答問題:什麼是觀察者模式已被登出 //問題是:什麼是觀察者模式 //學生1回答問題
但實際上這段程式碼使用的應該是釋出訂閱模式。接下來詳細瞭解一下二者的關係。
一、觀察者模式
在物件之間定義一個一對多的依賴,當物件自身狀態改變的時候,會自動通知給關心該狀態的觀察者。解決了主體物件與觀察者之間功能的耦合,即一個物件的狀態改變給其他物件通知的問題。
下面是一個簡單的觀察者模式例子:
// 被觀察者 class Subject { constructor() { // 觀察者列表 this.observers = [] } // 新增 add(observer) { this.observers.push(observer) } // 刪除 remove(observer) { let index = this.observers.indexOf(observer) if (index > -1) { this.observers.splice(index, 1) } } // 通知 notify() { for (let observer of this.observers) { observer.update() } } } // 觀察者類 class Observer { constructor(name) { this.name = name } // 觀察目標更新時 update() { console.log(`${this.name},專案又有新bug了`) } } let subject = new Subject() let obs1 = new Observer('前端開發') let obs2 = new Observer('後端開發') subject.add(obs1) subject.add(obs2) subject.notcify() // 前端開發,專案又有新bug了 // 後端開發,專案又有新bug了
在以上程式碼中可以很明顯的看出有兩個類,一個是Subject是被觀察者類,建構函式中有一個用來存放觀察者的列表,當被觀察者發生狀態改變時,通過遍歷被觀察者列表來通知觀察者。新增和刪除方法用來新增和刪除觀察者,notify用來通知觀察者。通過建立例項,呼叫被觀察者的notify方法來模擬被觀察者狀態改變時的事件呼叫。實現了簡單的觀察者模式。
二、釋出訂閱模式
也是定義一對多的依賴關係,物件狀態改變後,通知給所有關心這個狀態的訂閱者。釋出訂閱模式有訂閱的動作,可以不和觀察者直接產生聯絡,只要能訂閱上關心的狀態即可,通常可用第三媒介來做,而釋出者也會利用第三媒介來通知訂閱者。
以下是一個釋出訂閱者示例:
class PubSub { constructor() { this.list = {} } subscribe(key, fn) { if (!this.list[key]) { this.list[key] = [] } this.list[key].push(fn) } publish(key, ...arg) { for (let fn of this.list[key]) { fn.call(this, ...arg) } } unSubscribe(key, fn) { let fnList = this.list[key] if (!fnList) { return false } if (!fn) { fnList && (fnList.length = 0) } else { let index = fnList.indexOf(fn) fnList.splice(index, 1) } } } const pubSub = new PubSub() pubSub.subscribe('onwork', time => { console.log(`上班時間:${time}`) }) pubSub.subscribe('offwork', time => { console.log(`下班時間:${time}`) }) pubSub.subscribe('launch', time => { console.log(`午飯時間:${time}`) }) pubSub.publish('onwork', '8:30') pubSub.publish('launch', '12:00') pubSub.publish('offwork', '17:30') // 上班時間:8:30 // 午飯時間:12:00 // 下班時間:17:30
上面的程式碼就是釋出訂閱最重要的部分,雖然沒有明顯的實現觀察者類和被觀察者類,但觀察者和被觀察者在釋出訂閱模式中本來就不會有直接關聯,都只需呼叫以上程式碼(事件匯流排)中的釋出和訂閱方法即可。這樣的程式碼可以被提取出來,可以被重複使用,可以被多個觀察者和被觀察者使用。
三、兩者之間的異同
1、從概念上沒什麼不同,都是在解決物件之間解耦的問題,通過事件的方式在某個時間點進行觸發,監聽這個事件的訂閱者可以進行相應的操作。
2、從實現上來說,觀察者模式對訂閱事件的訂閱者通過釋出自身來維護,後續的一系列操作都需要通過釋出者完成;釋出訂閱模式是訂閱者和釋出者有一個事件匯流排,操作都要經過事件匯流排完成。
3、在觀察者模式中,觀察者知道被觀察的主體,主體也維護觀察者的記錄。在釋出訂閱模式中,釋出者和訂閱者不需要知道彼此,他們通過事件匯流排來實現通訊。觀察者模式進一步抽象,能抽出的公共程式碼就是事件匯流排。
4、觀察者模式是鬆耦合的,釋出訂閱模式,完全不耦合。
5、觀察者大多數主要以同步方式實現,釋出訂閱模式大多數是非同步模式。
6、觀察者模式基本用於單個應用內部,釋出訂閱模式更多的是跨應用的模式
Vue2中通過攔截資料進行依賴收集,等待資料變更時,通知依賴的Watcher進行元件更新。
通過使用Object.defineReactive()方法,對資料物件新增get()方法進行攔截,收集依賴,在通過新增set()方法,對修改資料進行攔截,通知依賴更新,從而達到資料的響應式。在此過程中也使用了釋出訂閱的模式,事件匯流排就是用來收集和通知更新依賴的Dep()函式。
// src/observer/dep.js export default class Dep{ static target: ?Watcher; id: number; subs: Array<Watcher>; constructor () { this.id = uid++ this.subs = [] } addSub(sub) { this.subs.push(sub) } removeSub(sub) { romove(this.subs, sub) } depend() { if (Dep.target) { Dep.target.addDep(this) } } notify() { const subs = this.subs.slice() for (let i = 0, l = subs.length; i < l; i++) { subs[i].update() } } }