觀察者模式和發佈訂閱模式(續)

兩年前寫過一篇關於消息發佈訂閱的文章,當時是結合了實際的應用場景。看起來不夠抽象,概括。今天試圖抽象話的再去寫一下自己的理解。

此文續上篇發佈 訂閱 消息系統(一),可點擊查看,取名改爲觀察者和發佈訂閱模式。

什麼是觀察者模式?

觀察者模式(Observer Pattern)定義了一種當對象之間存在一對多的關係時的一種行爲模式。比如當一個對象被修改時,則會通知它的依賴對象並自動更新狀態。它主要解決了一個對象狀態改變給其他對象通知的問題,並且考慮到易用和低耦合度,保證高度的協作性。

比如一個應用的實例:軍訓時,教官(目標對象)的狀態發生改變,發出“立正”的口令,則所有的學生(觀察者對象)此時得到通知,狀態隨之更新,做出"立正“的動作響應。

一個極簡的觀察者模式代碼案例

該案例的特徵:

  • 一個目標者對象 Subject,擁有方法:添加 / 刪除 / 通知 Observer;
  • 多個觀察者對象 Observer,擁有方法:接收 Subject 狀態變更通知並處理;
  • 目標對象 Subject 狀態變更時,通知所有 Observer。
//發佈者
class Subject {
	constructor() {
		this.observers = []
	}
	add(observer) {
		this.observers.push(observer)
	}
	delete(observer) {
		let idx = this.observers.findIndex( item => item === observer);
		idx > -1 && this.observers.splice(idx, 1)
	}
	notify() {
		for(let observer of this.observers) {
			observer.update()
		}
	}		
}

//觀察者
class Observer {
	constructor(name) {
		this.name= name
	}
	update() {
		console.log(`我是${this.name},我立正了`)
	}
}
// 實例化目標者
let subject = new Subject();

// 實例化兩個觀察者
let obs1 = new Observer('劉德華');
let obs2 = new Observer('吳彥祖');

// 向目標者添加觀察者
subject.add(obs1);
subject.add(obs2);

// 目標者通知更新
subject.notify();
//打印結果
//我是劉德華,我立正了
//我是吳彥祖,我立正了

上面代碼可以添加、刪除觀察者,當然你可以定製自己需求的功能函數。

當然這種模式是存在一些不足的,比如:

  • 一個目標者有很多觀察者的話,將所有通知者都通知到會花費不少時間
  • 若目標者和觀察者之間存在循環依賴的話,目標發生變化,觀察者變化,進而又影響目標者,處理不好,很容易導致系統崩潰。
  • 目標者通知更新的話,所有觀察者都接收到了通知。比如上面教官說立正,劉同學和吳同學都是進行相應,做出立正的動作。但實際情況是,教官可能只是指定劉同學 立正,只需劉同學做出響應更新而已。

什麼是發佈訂閱模式?

發佈訂閱模式: 訂閱者(Subscriber)把自己想訂閱的事件註冊(Subscribe)到調度中心(Topic),當發佈者(Publisher)發佈該事件(Publish topic)到調度中心,也就是該事件觸發時,由調度中心統一調度(Fire Event)訂閱者註冊到調度中心的處理代碼。

一個極簡的發佈訂閱模式案例

var pubsub = {
	//事件調度中心
    topics: {},
    //註冊事件到調度中心
    subscribe(topic, fn) {
        this.topics[topic] = this.topics[topic] || []
        this.topics[topic].push(fn)
    },
    //將事件發佈到調度中心
    publish(topic, ...args) {
        if (!this.topics[topic]) return
        for (let fn of this.topics[topic]) {
            fn(...args)
        }
    },
    //解綁到調度中心的事件
    unsubScribe(topic, fn) {
        let fnList = this.topics[topic]
        if (!fnList) return
        //若不傳入指定的要取消的訂閱的方法(fn),則清除所有topic下的訂閱
        if (!fn) {
            fnList && (fnList.length == 0)
        } else {
            fnList.forEach((item, index) => {
                if (item == fn) {
                    fnList.splice(index, 1)
                }
            })
        }
    }
}

//註冊eat事件
pubsub.subscribe('eat', time => {
    console.log(`now is ${time},time to eat lunch`);
})

//註冊work事件
pubsub.subscribe('work', time => {
    console.log(`now is ${time},time to work`);
})

//發佈work,eat事件
pubsub.publish('work', '8:30 AM') //now is 8:30 AM,time to work
pubsub.publish('eat', '12:30 AM') //now is 12:30 AM,time to eat lunch

//取消eat事件的訂閱
pubsub.unsubScribe('eat')

發佈訂閱模式與觀察者模式的不同,在於第三者的出現:事件中心(topic)的出現。目標對象並不直接通知觀察者,而是通過事件中心來派發事件。訂閱者只關心自己訂閱事件以此來響應變化。

就像上文說的那樣,DOM的監聽事件其實也是該模式的應用。比如jQuery的onclick事件,one()只觸發一次響應事件,off()解除綁定事件(類比上面代碼的unsubScribe事件)

借用一張圖表示如下:

在這裏插入圖片描述

觀察者模式是不是發佈訂閱模式

借用一個同學的總結

網上關於這個問題的回答,出現了兩極分化,有認爲發佈訂閱模式就是觀察者模式的,也有認爲觀察者模式和發佈訂閱模式是真不一樣的。

其實我不知道發佈訂閱模式是不是觀察者模式,就像我不知道辨別模式的關鍵是設計意圖還是設計結構(理念),雖然《JavaScript設計模式與開發實踐》一書中說了分辨模式的關鍵是意圖而不是結構

如果以結構來分辨模式,發佈訂閱模式相比觀察者模式多了一箇中間件訂閱器,所以發佈訂閱模式是不同於觀察者模式的;如果以意圖來分辨模式,他們都是實現了對象間的一種一對多的依賴關係,當一個對象的狀態發生改變時,所有依賴於它的對象都將得到通知,並自動更新,那麼他們就是同一種模式,發佈訂閱模式是在觀察者模式的基礎上做的優化升級。

不過,不管他們是不是同一個設計模式,他們的實現方式確實有差別,我們在使用的時候應該根據場景來判斷選擇哪個。


此文沒有長篇大論,力爭以最簡的話語表述我所理解的內容。其中借鑑了以下三位的同學的一些理解。如果看完還是雲裏霧裏,可以去看下他們的文章,寫的很不錯。

發佈訂閱模式與觀察者模式 https://segmentfault.com/a/1190000018706349 作者:hfhan

重學JS(九)—— 觀察者模式和發佈/訂閱模式真不一樣:https://www.jianshu.com/p/f0f22398d25d 作者:閃閃發光的狼

JavaScript 設計模式(六):觀察者模式與發佈訂閱模式 https://segmentfault.com/a/1190000019722065 作者: 以樂之名

發佈了38 篇原創文章 · 獲贊 39 · 訪問量 8萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章