一解取、定義
定義對(duì)象間的一種一對(duì)多的依賴關(guān)系斩松,當(dāng)一個(gè)對(duì)象的狀態(tài)發(fā)生改變時(shí)络凿,所有依賴于它的對(duì)象都得到通知并被自動(dòng)更新荞彼。
在這里先講一下面向?qū)ο笤O(shè)計(jì)的一個(gè)重要原則——單一職責(zé)原則冈敛。因此系統(tǒng)的每個(gè)對(duì)象應(yīng)該將重點(diǎn)放在問題域中的離散抽象上。因此理想的情況下鸣皂,一個(gè)對(duì)象只做一件事情抓谴。這樣在開發(fā)中也就帶來了諸多的好處:提供了重用性和維護(hù)性,也是進(jìn)行重構(gòu)的良好的基礎(chǔ)寞缝。幾乎所有的設(shè)計(jì)模式都是基于這個(gè)基本的設(shè)計(jì)原則來的癌压。
觀察者模式(又被稱為發(fā)布-訂閱(Publish/Subscribe)模式。說到發(fā)布訂閱荆陆,最熟悉的就是我們的微信公眾號(hào)了措拇,那就用這個(gè)來舉例子:
觀察者模式的簡(jiǎn)單實(shí)現(xiàn)
假設(shè)微信用戶就是觀察者,微信公眾號(hào)是被觀察者慎宾,有多個(gè)的微信用戶關(guān)注了程序猿這個(gè)公眾號(hào)丐吓,當(dāng)這個(gè)公眾號(hào)更新時(shí)就會(huì)通知這些訂閱的微信用戶。
先實(shí)現(xiàn)微信公眾號(hào)的類
/* 所有公眾號(hào) */
class Pubsub {
/*
follows 保存公眾號(hào)下的用戶和用戶的操作趟据,數(shù)據(jù)結(jié)構(gòu)如下:
{
["Github最新開源項(xiàng)目"]: [ {id: 0, fn: fn} , {id: 1, fn: fn} , {fn: fn} ],
["CNode社區(qū)新聞"]: [ {id: 0, fn: fn} , {id: 1, fn: fn} , {fn: fn} ],
}
* */
constructor (){
this.follows = {}
this.id = -1
}
/* 訂閱方法
* @param {string} userName 公眾號(hào)名
* @param {function} fn 公眾號(hào)發(fā)布文章后用戶會(huì)采取的操作
* @return {string} id 每個(gè)用戶在公眾號(hào)中的唯一標(biāo)識(shí)
* */
subscrilb(pubsubName, fn) {
this.follows[pubsubName] || (this.follows[pubsubName] = [])
let id = '' + (++this.id)
this.follows[pubsubName].push({ id, fn })
return id
}
/* 發(fā)布方法
* @param {string} userName 公眾號(hào)名
* */
publish (pubsubName) {
let len = this.follows[pubsubName].length;
for (let i = 0; i < len; i++) {
console.log(this.follows)
this.follows[pubsubName][i].fn()
}
}
/* 取消訂閱方法
* @param {string} userName 公眾號(hào)名
* @return {string} id 每個(gè)用戶在公眾號(hào)中的唯一標(biāo)識(shí)
* */
unsubscribe(pubsubName, id) {
for (let key of this.follows) {
if(key == pubsubName) {
for (let i = 0,len = this.follows[pubsubName].length; i< len; i++) {
if (this.follows[pubsubName][i].id === id) {
this.follows[pubsubName].splice(i, 1)
}
}
}
}
}
}
let pubsub = new Pubsub();
接下來設(shè)置用戶類
class User {
constructor(name){
this.name = name
}
/*
* 用戶訂閱方法
**/
follow(pubsubName, fn) {
pubsub.subscrilb(pubsubName, fn)
}
}
let user1 = new User('user-1')
let user2 = new User('user-2')
然后進(jìn)行訂閱發(fā)布
user1.follow('CNode社區(qū)新聞',function() {
console.log('user 1 關(guān)注此社區(qū)券犁!')
})
user2.follow('CNode社區(qū)新聞',function() {
console.log('user 2 關(guān)注此社區(qū)!')
})
pubsub.publish('CNode社區(qū)新聞')
user1.follow('Github最新咨詢',function() {
console.log('user 1 關(guān)注此社區(qū)汹碱!')
})
user2.follow('Github最新咨詢',function() {
console.log('user 2 關(guān)注此社區(qū)粘衬!')
})
pubsub.publish('Github最新咨詢')
觀察者模式優(yōu)缺點(diǎn)
優(yōu)點(diǎn):
我們作為訂閱者不必每次都去查看這個(gè)公眾號(hào)有沒有新文章發(fā)布, 公眾號(hào)作為發(fā)布者會(huì)在合適時(shí)間通知我們
我們與公眾號(hào)之間不再?gòu)?qiáng)耦合在一起。公眾號(hào)不關(guān)心誰訂閱了它稚新,
不管你是男是女還是寵物狗勘伺,它只需要定時(shí)向所有訂閱者發(fā)布消息即可可以廣泛應(yīng)用于異步編程,它可以代替我們傳統(tǒng)的回調(diào)函數(shù)
我們不需要關(guān)注對(duì)象在異步執(zhí)行階段的內(nèi)部狀態(tài)褂删,我們只關(guān)心事件完成的時(shí)間點(diǎn)
缺點(diǎn):
- 在應(yīng)用觀察者模式時(shí)需要考慮一下開發(fā)效率和運(yùn)行效率的問題飞醉,程序中包括一個(gè)被觀察者、多個(gè)觀察者屯阀,開發(fā)缅帘、調(diào)試等內(nèi)容會(huì)比較復(fù)雜
- 由于JavaScript單線程異步機(jī)制,即使一個(gè)觀察者卡頓了难衰,也不會(huì)影響整體的執(zhí)行效率钦无。(多線程同步便會(huì)阻塞)
總結(jié)
觀察者模式有兩個(gè)明顯的優(yōu)點(diǎn)
- 時(shí)間上解耦
- 對(duì)象上解耦
關(guān)于觀察者模式,在瀏覽器和Node都有良好的事件機(jī)制支持盖袭,不必自己實(shí)現(xiàn)失暂,本文只是簡(jiǎn)單了解。