【设计模式】发布订阅模式,构建可以接收历史消息的观察者类
2024/04/25 14:59:242022/12/26 15:29:13
什么是发布订阅模式?
发布订阅模式也被称为观察者模式,这个模式中有两种角色:发布者(被观察者)
和 订阅者(观察者)
。
通常的操作是:订阅者订阅发布者的某一个事件,发布者接收到这个事件变化的时候,通知所有的订阅者。
举个栗子:你在微信中关注了一个公众号,这个公众号在更新文章的时候,微信会将新的文章推送到你的微信消息中,公众号是发布者,你的微信号是订阅者。
你可以关注多个公众号,公众号也可以有多个粉丝。
一个简单的观察者类
class Observer {
/**
* 事件队列
*/
static events = {};
/**
* 订阅
* @param {*} topic
* @param {*} callback
*/
static subscribe(topic, callback) {
if (!Observer.events[topic]) {
Observer.events[topic] = [];
}
Observer.events[topic].push(callback);
return () => {
const index = Observer.events[topic].findIndex(
(item) => item === callback
);
Observer.events[topic].splice(index, 1);
};
}
/**
* 发布
* @param {*} topic
* @param {*} data
*/
static publish(topic, data) {
if (Observer.events[topic]) {
Observer.events[topic].forEach((fn) => {
fn(data);
});
} else {
console.error(`topic [${topic}] is empty!`);
}
}
}
订阅消息:
// 订阅消息
const unsubscribe = Observer.subscribe("update", (data) => {
console.log(data);
});
// 取消订阅
unsubscribe();
发布消息:
Observer.publish("update", 1);
可以接收历史消息的观察者类
上面的观察者无法接收历史消息,如果一个消息在未订阅时就已经发布,那么这条消息就会被漏掉。
一个可能的场景是:在一个页面中,需要在导航栏中展示用户信息,用户信息需要通过网络请求来获取,获取到后通过 Observer.publish()
方法发布,导航栏通过 Observer.subscribe()
方法来获取用户信息及回调。
这个场景中如果用户信息已经获取,但是导航栏组件还未加载,这种情况下导航栏就再也拿不到用户信息了。
解决方案就是维护一个历史消息列表,在调用 Observer.subscribe()
订阅消息时如果有历史消息则立即触发回调。
class Observer {
/**
* 历史消息
*/
static history = {};
/**
* 事件队列
*/
static events = {};
/**
* 订阅
* @param {*} topic
* @param {*} callback
*/
static subscribe(topic, callback) {
if (!Observer.events[topic]) {
Observer.events[topic] = [];
}
Observer.events[topic].push(callback);
// 如果有历史消息
if (Observer.history[topic]) {
callback(Observer.history[topic]);
}
return () => {
const index = Observer.events[topic].findIndex(
(item) => item === callback
);
Observer.events[topic].splice(index, 1);
};
}
/**
* 发布
* @param {*} topic
* @param {...any} args
*/
static publish(topic, data) {
if (Observer.events[topic]) {
Observer.events[topic].forEach((fn) => {
fn(data);
});
}
if (!Observer.history[topic]) {
Observer.history[topic] = [];
}
// 保存历史消息
Observer.history[topic].push(data);
}
}
可以先发布后订阅:
Observer.publish("update", 222);
Observer.publish("update", 333);
const unsubscribe = Observer.subscribe("update", (data) => {
if (Array.isArray(data)) {
// 历史消息列表
} else {
// 最新消息
}
console.log(data);
});
Observer.publish("update", 444);
总结
发布订阅模式的优点是可以很方便的实现不同模块之间的通信。
它的缺点在于,观察者对象本身是占用内存的,而且当你订阅一个消息后,也许此消息再也没有发布过,但这个观察者对象会始终存在于内存中。
发布订阅模式弱化了对象之间的联系,但如果过度使用的话,对象和对象之间的必要联系也将被深埋在背后,会导致程序难以跟踪维护和理解。
当多个发布者和订阅者嵌套到一起的时候,很难捋清楚他们之间的关系。
可以用,但别到处都用。