題意描述:
觀察者模式和發佈訂閱模式有什麼不同 ? 手寫一個觀察者模式的例子 ?
解題思路:
Alice: 上次講了觀察者模式,發佈訂閱模式是什麼 ?
Bob: 和觀察者模式很類似,發佈訂閱模式其實屬於廣義上的觀察者模式。在觀察者模式中,觀察者需要直接訂閱目標事件。在目標發出內容改變的事件後,直接接收事件並作出響應。而在發佈訂閱模式中,發佈者和訂閱者之間多了一個調度中心。調度中心一方面從發佈者接收事件,另一方面向訂閱者發佈事件,訂閱者需要在調度中心中訂閱事件。通過調度中心實現了發佈者和訂閱者關係的解耦。使用發佈訂閱者模式更利於我們代碼的可維護性。
Alice: 也就是說發佈訂閱模式就是在 觀察者模式的 被觀察者 和 觀察者之間加了一層 調度中心 ?
Bob: 是的,下面的圖解釋的很清楚,就是多了一個調度中心。即實現了 發佈者 和 訂閱者 之間的解耦,還可以在 調度中心加一些細粒度的控制,就是代碼可能會麻煩一點。
Alice: 寫段代碼來遛一遛 😏
Bob: 我來一個發佈訂閱模式吧。
// eventChannel 後是一個 IIEF 立即執行的函數表達式
var eventChannel = (function () {
var events = {};
// 閉包,用於存儲調度中心接收的消息
return {
// 訂閱者通過 subscribe 函數訂閱event事件
subscribe: function (event, handler) {
if (!events.hasOwnProperty(event)) {
events[event] = [];
}
events[event].push(handler);
},
receiveEvent: function (event) {
if (events.hasOwnProperty(event)) {
console.log(`非首次接收 ${event}, 嘗試 FireEvent`);
this.fireEvent(event, "from receiveEvent");
} else {
console.log(`首次接收 ${event}`);
events[event] = [];
}
},
// 調度中心選擇 觸發事件處理函數
fireEvent: function (event, msg) {
if (events.hasOwnProperty(event)) {
// 有對應的事件處理函數
events[event].forEach(handler => {
handler(msg);
// 調用每個事件處理函數
});
}
},
remove: function (event, handler) {
// 移除事件的某一個處理函數
if (events.hasOwnProperty(event)) {
let index = events[event].indexOf(handler);
// 放心,indexOf 使用的是 ===
if (index !== -1) {
events[event].splice(index, 1);
}
}
},
removeAll: function (event) {
// 移除某個事件的所有處理函數
if (events.hasOwnProperty(event)) {
events[event] = [];
}
}
}
})();
var handler = function (msg) {
console.log(`handler is running : ${msg}`);
}
// publisher 通過 receiveEvent 來發布事件
eventChannel.receiveEvent('AREUOK');
eventChannel.subscribe('AREUOK', handler);
eventChannel.receiveEvent('AREUOK');
eventChannel.fireEvent('AREUOK', 'Year, I AM OK');
// 首次接收 AREUOK
// 非首次接收 AREUOK, 嘗試 FireEvent
// handler is running: from receiveEvent
// handler is running: Year, I AM OK
Alice: 你這裏是沒有寫 發佈者 publisher 嗎,還有訂閱者 subscriber 也沒寫 。
Bob: 不過我寫了 調度中心和 二者 交互的函數呀, receiveEvent, subscribe, fireEvent 應該能夠展示 發佈訂閱模式的工作機理了,發佈者和訂閱者只需要調用對應的函數就好了。
Alice: 不錯不錯,我來一個 觀察者模式吧。
var Observer = function(name){
// 觀察者的構造函數
this.name = name;
this.update = function(msg){
console.log(`${this.name} get msg: ${msg}`);
}
}
var Subject = function(name){
// 被觀察者構造函數
this.name = name;
this.observers = [];
this.addObserver = function(observer){
this.observers.push(observer);
}
this.removeObserver = function(observer){
let index = this.observers.indexOf(observer);
if(index !== -1){
this.observers.splice(index, 1);
}
}
this.notifyAll = function(){
this.observers.forEach(observer => {
observer.update(this.name + " ~ msg by nofifyAll");
});
}
}
var ob1 = new Observer('Alice'),
ob2 = new Observer('Bob');
var sub = new Subject('winter is coming');
sub.addObserver(ob1);
sub.addObserver(ob2);
sub.notifyAll();
// Alice get msg: winter is coming ~msg by nofifyAll
// Bob get msg: winter is coming ~msg by nofifyAll
代碼:
- 在上面
易錯點:
- 觀察者模式 和 訂閱發佈模式 是有區別的。
總結:
- 在觀察者模式中,觀察者是知道Subject的,Subject一直保持對觀察者進行記錄。然而,在發佈訂閱模式中,發佈者和訂閱者不知道對方的存在。它們只有通過調度中心進行通信。
- 在發佈訂閱模式中,組件是鬆散耦合的,正好和觀察者模式相反。
- 觀察者模式大多數時候是同步的,比如當事件觸發,Subject就會去調用觀察者的方法。而發佈-訂閱模式大多數時候是異步的(使用消息隊列)
參考文獻: