跳到主要内容

发布订阅

发布订阅模式解决什么问题?​

  • 问题: 消息的发布方与接收方不应互相知道对方的存在;
  • 解法: 在两者之间加入订阅中心, 发布方按主题发布, 订阅中心按主题把消息转发给订阅者;
  • 收益: 发布方与订阅者彻底解耦, 可一对多广播, 也可动态增删订阅;

发布订阅模式包含哪些角色?​

  • Publisher: 发布者, 负责把消息交给订阅中心;
  • ConcretePublisher: Publisher 的具体实现;
  • MessageCenter: 订阅中心, 定义 subscribe、unsubscribe、publish;
  • Subscriber: 订阅者, 定义订阅、取消订阅与更新方法;

发布订阅与观察者模式有什么区别?​

  • 观察者模式: 观察者知道自己的目标, 由目标直接通知观察者;
  • 发布订阅模式: 发布者不认识订阅者, 消息经订阅中心转发;
  • 关系: 发布订阅可以看作在观察者之上又增加了一层中介, 见 观察者;

如何用 TypeScript 实现事件订阅中心?​

interface SubscribeEvent {
fn: (...args: any[]) => void;
once: boolean;
}

class EventEmitter {
private subscribes = new Map<string, Set<SubscribeEvent>>();

private addEvent(type: string, fn: (...args: any[]) => void, once: boolean) {
let sub = this.subscribes.get(type);
if (!sub) {
sub = new Set();
this.subscribes.set(type, sub);
}
sub.add({ fn, once });
}

on(type: string, callback: (...args: any[]) => void) {
this.addEvent(type, callback, false);
}

once(type: string, callback: (...args: any[]) => void) {
this.addEvent(type, callback, true);
}

emit(type: string, ...args: any[]) {
const sub = this.subscribes.get(type);
if (!sub) return;
for (const event of [...sub]) {
event.fn(...args);
if (event.once) sub.delete(event);
}
}

off(type: string, callback: (...args: any[]) => void) {
const sub = this.subscribes.get(type);
if (!sub) return;
for (const event of sub) {
if (event.fn === callback) sub.delete(event);
}
}
}

如何使用事件订阅中心?​

const emitter = new EventEmitter();

emitter.once("once", () => console.log("once test"));
emitter.emit("once"); // once test
emitter.emit("once"); // 不再触发

const fn = () => console.log("off");
emitter.on("on", fn);
emitter.on("on", () => console.log("no off"));
emitter.off("on", fn);
emitter.emit("on"); // no off
  • 遍历安全: emit 先复制订阅集合再执行回调, 避免回调中增删订阅影响本次遍历;