這兩個東西捋清楚需要點耐心。
1、觀察者vs釋出訂閱
- 首先觀察者跟釋出訂閱這兩者雖然看起來很相似,但是兩者是有差異的,至少從實作方式上就有差異。
- 其次,雖然兩者有差異,但是說釋出訂閱模式是觀察者模式的變異也是ok的,因為它們思想上是一緻的。
- 最後,做個類比,觀察者模式類似于
,釋出訂閱模式類似于房東—租客
,本文将圍繞這兩個模型來探讨實作。房東—中介—租客

觀察者vs釋出訂閱
- 下面我們就用結合代碼來捋一捋。
2、觀察者模式
- 既然我們把觀察者模式類比為
的這種關系模型,那我們就來想象一下,這個房東是某拆遷戶,名下出租的房子有房東——使用者
、别墅(bigHouse)
、洋房(mediumHouse)
等三種戶型出租,他微信裡面有很多租客,分有錢的、沒錢的等等,這些是前提。平房(smallHouse)
- 首先我們來定義一下租客。(你先不管裡面為什麼要寫這些方法屬性,後面把這個對象執行個體化變成個人以後會闡述的,你先看,看完了再回頭review一下)
// 觀察者(租客)
class Observer {
constructor(subject) {
this.subject = subject;
}
notify() {
console.log(`收到一條房東的消息,${this.subject}空了!!!`);
}
}
複制
- 然後我們需要描述下房東:
// 主題(房東)
class Subject {
// 根據戶型的不同收集相應的訂閱者
constructor() {
this.subjectList = {};
}
// 訂閱
add(subject, observer) {
if (!this.subjectList[subject]) {
this.subjectList[subject] = [];
}
this.subjectList[subject].push(observer);
}
// 解除訂閱
remove(subject, observer) {
this.subjectList[subject].forEach((item, index) => {
if (item === observer) {
this.subjectList[subject].splice(index, 1);
}
});
}
// 觸發事件
fire(subject) {
this.subjectList[subject].forEach((item) => item.notify());
}
}
複制
- Ok,接下來有這麼三位觀察者租客ABC,他們分别想租
、别墅(bigHouse)
和洋房(medium)
。平房(small)
// 不同的使用者想關注别墅(big)、`洋房(medium)`...等不同的戶型
const observerA = new Observer("bigHouse");
const observerB = new Observer("mediumHouse");
const observerC = new Observer("smallHouse");
複制
- 當ABC這三個租客向房東表達了他們的意向以後,房客加他們微信的時候就對其分了不同的組:
// 把這3個觀察者添加到相應的分組
const subjects = new Subject();
subjects.add("bigHouse", observerA);
subjects.add("mediumHouse", observerB);
subjects.add("smallHouse", observerC);
複制
- 某一天,别墅空了,于是房東對
的小夥伴們群發了一條消息,于是你就收到了推送;又某一天,洋房空了。。。别墅組
// 某一天
subjects.fire("bigHouse"); // 收到一條房東的消息,bigHouse空了!!!
// 又某一天
subjects.fire("mediumHouse"); // 收到一條房東的消息,mediumHouse空了!!!
// 再某一天
subjects.fire("smallHouse"); // 收到一條房東的消息,smallHouse空了!!!
複制
- 這就是觀察者模式,完整代碼如下:
// 觀察者(租客)
class Observer {
constructor(subject) {
this.subject = subject;
}
notify() {
console.log(`收到一條房東的消息,${this.subject}空了!!!`);
}
}
// 主題(房東)
class Subject {
// 根據戶型的不同收集相應的訂閱者
constructor() {
this.subjectList = {};
}
// 訂閱
add(subject, observer) {
if (!this.subjectList[subject]) {
this.subjectList[subject] = [];
}
this.subjectList[subject].push(observer);
}
// 解除訂閱
remove(subject, observer) {
this.subjectList[subject].forEach((item, index) => {
if (item === observer) {
this.subjectList[subject].splice(index, 1);
}
});
}
// 觸發事件
fire(subject) {
this.subjectList[subject].forEach((item) => item.notify());
}
}
// 不同的使用者想關注别墅(big)、`洋房(medium)`...等不同的戶型
const observerA = new Observer("bigHouse");
const observerB = new Observer("mediumHouse");
const observerC = new Observer("smallHouse");
// 把這3個觀察者添加到相應的分組
const subjects = new Subject();
subjects.add("bigHouse", observerA);
subjects.add("mediumHouse", observerB);
subjects.add("smallHouse", observerC);
// 某一天
subjects.fire("bigHouse"); // 收到一條房東的消息,bigHouse空了!!!
// 又某一天
subjects.fire("mediumHouse"); // 收到一條房東的消息,mediumHouse空了!!!
// 再某一天
subjects.fire("smallHouse"); // 收到一條房東的消息,smallHouse空了!!!
複制
3、釋出訂閱模式
(1)了解
- 當你了解了觀察者模式
這種模型以後,你會發現,如果觀察者很多,那麼房東壓力還是挺大的,比如收錢的壓力。房東—租客
- 這個時候,房東每天簽合同、收房租跑斷腿,不堪其擾,于是就去拜托中介,交給中介打理省心,于是就有了類似于
的這種釋出訂閱模式。房東—中介—租客
(2)實踐
- 首先我們需要定義一下
:中介機構
class EventBus {
constructor() {
// 定義事件池
// 資料結構應該長這樣:{<事件隊列名>:<要執行的事件隊列>}
// {'buy': ['func1','func2','func3']}
this.events = {};
}
/**
* 訂閱
* @param {事件隊列名} name
* @param {事件函數} func
*/
on(name, func) {
if (!this.events[name]) {
this.events[name] = [];
}
this.events[name].push(func);
}
/**
* 釋出
* @param {事件隊列名} args
* @param {傳入參數} args
*/
emit(name, ...args) {
if (!this.events[name]) {
return;
}
this.events[name].forEach(item => {
item.apply(this, args);
});
}
/**
* 删除
* @param {事件隊列名} args
*/
remove(name) {
if (this.events[name]) {
delete this.events[name];
}
}
}
複制
- 然後
通過中介機構根據自己的需要訂閱了不同的房型。租客
// 執行個體化
const bus = new EventBus();
// 不同租客訂閱了不同需求房型
bus.on("bigHouse", function (value) {
console.log(`A收到了一條消息:${value}`);
});
bus.on("mediumHouse", function (value) {
console.log(`B收到了一條消息:${value}`);
});
bus.on("smallHouse", function (value) {
console.log(`C收到了一條消息:${value}`);
});
複制
- 某一天,
就直接通過中介發消息了,說:房東
// 某天房東通過中介釋出了一條消息
bus.emit("bigHouse", "别墅有房了"); // A收到了一條消息:别墅有房了
// 又某一天
bus.emit("mediumHouse", "洋房有房了"); // B收到了一條消息:洋房有房了
// 再某一天。。。。
bus.emit("smallHouse", "小平層有房了"); // C收到了一條消息:小平層有房了
複制
- 完整代碼:
class EventBus {
constructor() {
// 定義事件池
// 資料結構應該長這樣:{<事件隊列名>:<要執行的事件隊列>}
// {'say': ['func1','func2','func3']}
this.events = {};
}
/**
* 訂閱
* @param {事件隊列名} name
* @param {事件函數} func
*/
on(name, func) {
if (!this.events[name]) {
this.events[name] = [];
}
this.events[name].push(func);
}
/**
* 釋出
* @param {事件隊列名} args
* @param {傳入參數} args
*/
emit(name, ...args) {
if (!this.events[name]) {
return;
}
this.events[name].forEach((item) => {
item.apply(this, args);
});
}
/**
* 删除
* @param {事件隊列名} args
*/
remove(name) {
if (this.events[name]) {
delete this.events[name];
}
}
}
const bus = new EventBus();
// 不同租客訂閱了不同需求房型
bus.on("bigHouse", function (value) {
console.log(`A收到了一條消息:${value}`);
});
bus.on("mediumHouse", function (value) {
console.log(`B收到了一條消息:${value}`);
});
bus.on("smallHouse", function (value) {
console.log(`C收到了一條消息:${value}`);
});
// 某天房東通過中介釋出了一條消息
bus.emit("bigHouse", "别墅有房了"); // A收到了一條消息:别墅有房了
// 又某一天
bus.emit("mediumHouse", "洋房有房了"); // B收到了一條消息:洋房有房了
// 再某一天。。。。
bus.emit("smallHouse", "小平層有房了"); // C收到了一條消息:小平層有房了
複制
回顧一下,從這個例子就可可以看到:
-
這個就是相當于扮演了EventBus
的角色。中介機構
-
就相當于是emit釋出
。房東不定時随機的釋出消息,說某某套房子空了。房東
-
就相當于是on訂閱
,訂閱某個戶型的事件了以後就可以實時收到該戶型的通知。房客
4、小結
- 通過上面這些示例,兩者之間最大的差別就是
這一環,通過這個機構,房東和房客之間的溝通更加的順暢了,也就是兩者之間中介結構
,這樣的話,我們開發的時候可以将松耦合
抽離成為了一個單獨的檔案,這樣使得業務邏輯更加清晰,維護起來也更加的差別。不管這兩者模式是相同還是不同的,這個我覺得是最主要的差別。中介結構
- 其它的我這裡直接引用這篇文章的小結概況一下吧,我覺得這位大佬總結還是比較到位的:
- 在觀察者模式中,觀察者是知道Subject的,Subject一直保持對觀察者進行記錄。然而,在釋出訂閱模式中,釋出者和訂閱者不知道對方的存在。它們隻有通過消息代理進行通信。
- 在釋出訂閱模式中,元件是松散耦合的,正好和觀察者模式相反。
- 觀察者模式大多數時候是同步的,比如當事件觸發,Subject就會去調用觀察者的方法。而釋出-訂閱模式大多數時候是異步的(使用消息隊列)。
- 觀察者 模式需要在單個應用程式位址空間中實作,而釋出-訂閱更像交叉應用模式。
- 當然,以上這些都是我自己的了解,歡迎交流。
參考學習:
https://juejin.im/post/5a14e9edf265da4312808d86
https://molunerfinn.com/observer-vs-pubsub-pattern
https://juejin.im/post/5bb1bb616fb9a05d2b6dccfa
https://juejin.im/post/57de12355bbb50005e648bd8