引言
发布订阅模式(Publish-Subscribe Pattern),也称为Pub-Sub模式,是一种软件架构模式,它定义了一种事件驱动的通信机制,允许多个对象(称为“订阅者”)以“订阅”的方式表达对特定事件的兴趣,并且当这些事件发生时,事件的“发布者”会自动将事件通知给所有已订阅的订阅者。
效果体验
实体关系图
Subscriber: 订阅者-对事件感兴趣的对象,它们注册自己到事件通道对象中以接收来自发布者的事件通知
Event Channel: 事件通道-管理订阅者列表并负责将接受发布者通知以调用订阅者的事件
Publisher: 发布者-创建事件并将其通知给事件通道对象
erDiagram
"Subscriber" {
string event_name "事件名"
function callback_method "事件的方法"
}
"Event Channel" {
string event_name "事件名"
set subscribers "同名事件回调方法集合"
}
"Publisher" {
string event_name "事件名"
array data "传递参数数据"
}
"Subscriber" ||--o{ "Event Channel" : "subscribes"
"Publisher" ||--o{ "Event Channel" : "publishes"
"Event Channel" ||--o{ "Subscriber" : "triggers"
发布订阅模式实现
/**
* EventCallback 事件回调函数。
*/
type EventCallback = (...args: any[]) => void;
/**
* EventManager 管理事件的发布和订阅。
*/
class EventManager {
/**
* 私有静态属性,存储 EventManager 的单例实例。
* @private
*/
static #instance: EventManager;
/**
* 存储事件监听器的映射表,键为事件名称,值为该事件的监听器集合。
* @private
*/
#listeners: Record<string, Set<EventCallback>> = {};
/**
* 构造函数是私有的,以确保只能通过 getInstance 方法获取 EventManager 的实例。
*/
private constructor() {}
/**
* 获取 EventManager 的单例实例。
* @returns EventManager 的单例实例。
*/
static getInstance() {
return EventManager.#instance ??= new EventManager();
}
/**
* 注册事件监听器。
* @param event 事件名称。
* @param listener 事件触发时调用的函数。
*/
on(event: string, listener: EventCallback) {
;(this.#listeners[event]??= new Set()).add(listener);
}
/**
* 触发指定事件,调用所有注册的监听器。
* @param event 事件名称。
* @param args 传递给监听器的参数。
*/
emit(event: string, ...args: any[]) {
this.#listeners[event]?.forEach(cb=>cb(...args))
}
/**
* 移除事件监听器。
* @param event 事件名称。
* @param listener 要移除的监听器函数。
*/
off(event: string, listener: EventCallback) {
this.#listeners[event]?.delete(listener);
}
/**
* 注册单次事件监听器,事件触发后自动移除。
* @param event 事件名称。
* @param listener 事件触发时调用的函数。
*/
once(event: string, listener: EventCallback) {
const wrapper = (...args: any[])=>{
listener(...args);
this.off(event, wrapper);
}
this.on(event, wrapper);
}
}
FAQ🙋
Q: 如何实现先发布后订阅,也可以触发事件
A: 再维护一个事件参数对象,当发布事件(emit),如果还没有订阅者,可以将发布的事件名称和参数存在新维护的事件参数对象中,等订阅者订阅时,判断事件参数对象中的是否有对应事件,有即执行,执行结束删除该事件的事件参数对象,保证执行一次.
/**
* 扩展需要根据自身业务需求来定义
* 实现中使用 Set 存订阅事件及先发布后订阅的事件参数,想的是去重,
* 重复订阅事件, 先发布后订阅多次相同参数的事件 都只存一次
* 可以根据业务需求,来决定用Array存还是Set存
*/
type EventCallback = (...args: any[]) => void;
class EventManager {
static #instance: EventManager;
#listeners: Record<string, Set<EventCallback>> = {};
#pending: Record<string, Set<any[]>> = {}
private constructor() {}
static getInstance() {
return EventManager.#instance ??= new EventManager();
}
on(event: string, listener: EventCallback) {
;(this.#listeners[event]??= new Set()).add(listener);
if(this.#pending[event]){
this.#pending[event].forEach((args)=>listener(...args));
delete this.#pending[event];
}
}
emit(event: string, ...args: any[]) {
if (this.#listeners[event]) {
this.#listeners[event].forEach(cb=>cb(...args))
} else {
;(this.#pending[event]??=new Set()).add(args)
}
}
off(event: string, listener: EventCallback) {
this.#listeners[event]?.delete(listener);
}
once(event: string, listener: EventCallback) {
const wrapper = (...args: any[])=>{
listener(...args);
this.off(event, wrapper);
}
this.on(event, wrapper);
}
}
Q: 如何实现先发布的事件设置有效期,在有效期内,有新订阅者就触发事件,超过有效期,再有新订阅者不触发事件
Q: 如何实现发布一条永久事件,不论什么时候新增订阅者都要处理这个事件
感谢阅读,敬请斧正!