发布订阅与观察者模式总是被混淆,不容易分辨,其实这两种模式最初都是同一种,之后进行了一些发展与演变,才有了发布订阅这种解耦的方式,就自己的理解谈一谈这两种方式的具体区别。
两者的区别
发布订阅可以说是观察者模式的一种升级
观察者模式核心是我知道你的存在,当发生了变化时我直接通知大家发生的变化。
发布订阅模式核心是我不知道其他人是否存在,我需要告诉第三方有事件了,第三方负责去通知各个订阅人,同理各个订阅人也只是订阅了,具体会不会发生这个事件订阅人也不知道。
观察者模式是松耦合的,发布订阅是解耦合的。
解耦合当然是好事情了,但是还是需要去看具体的场景了,如果彻底解耦就需要第三方来帮助我们对接,那么无疑会增加我们的代码
如果是大型系统的对接,我们就需要使用解耦合的场景,未来我们也不知道会有那些人来使用,会有谁接入进来,所以需要一个第三方平台来管控,是比较便于后续系统的继续拓展的。
如果是我们自己内部代码,那么松耦合也是可以接受的,我们在编写代码就能掌控到全局,为了代码模块今后的拓展性,采用松耦合的方式也完全是ok的。
当发生取消操作时(不订阅),观察者模式主体是需要亲自操作的,而发布订阅模式发布者是不需要感知的。
JS简单实现两种方式
熟悉ts,推荐使用ts去实现,对于理解原理会更好一点。
观察者模式
核心代码
// 观察者
class Observer {
update(payload) { }
}
// 事件主体
class Subject {
constructor() {
this._observers = []
}
add(observer) {
this._observers.push(observer)
}
remove(observer) {
const removeIndex = this._observers.indexOf(observer);
if (removeIndex !== -1) {
this._observers.splice(removeIndex, 1);
}
}
notify(payload) {
this._observers.forEach(observer => observer.update(payload));
}
}
使用示例
创建一个前端主题,并创建出多个开发者,最终前端更新主题,直接通知到开发者
class FEObserver extends Observer {
constructor(name) {
super();
this.name = name;
}
update(payload) {
console.log(`${this.name}学习了-${payload.type}`);
}
}
const feSubject = new Subject<FEPayloadType>();
const fe1 = new FEObserver('fe1');
const fe2 = new FEObserver('fe2');
feSubject.add(fe1);
feSubject.add(fe2);
feSubject.notify({ type: 'CSS' });
// fe1学习了-CSS
// fe2学习了-CSS
feSubject.remove(fe2);
feSubject.notify({ type: 'JS' });
// fe1学习了-JS
发布订阅模式
核心代码
class EventEmitter {
constructor() {
this.c = new Map();
}
// 订阅指定的主题
subscribe(topic, ...handlers) {
let topics = this.c.get(topic);
if (!topics) {
this.c.set(topic, topics = []);
}
topics.push(...handlers);
}
// 取消订阅主题
unsubscribe(topic, handler) {
if (!handler) {
return this.c.delete(topic);
}
const topics = this.c.get(topic);
if (!topics) {
return false;
}
const removeIndex = topics.indexOf(handler);
if (removeIndex < 0) {
return false;
}
topics.splice(removeIndex, 1);
if (topics.length === 0) {
this.c.delete(topic);
}
return true;
}
// 发布主题
publish(topic, ...args) {
const topics = this.c.get(topic);
if (!topics) {
return null;
}
return topics.map(handler => {
try {
return handler(...args);
} catch (e) {
console.error(e);
return null;
}
});
}
}
使用示例
创建一个ts学习主题,进行一下订阅,之后统一发布到EventCenter
const eventCenter = new EventEmitter();
const subscriberTs1 = (content) => {
console.log(`subscriberTs1 receive: ${content}`);
};
const subscriberTs2 = (content) => {
console.log(`subscriberTs2 receive: ${content}`);
};
// 订阅事件
eventCenter.subscribe('ts', subscriberTs1);
eventCenter.subscribe('ts', subscriberTs2);
// 发布事件
eventCenter.publish('ts', '发布了一篇ts文章');
// subscriberTs1 receive: 发布了一篇ts文章
// subscriberTs2 receive: 发布了一篇ts文章
// 取消订阅
eventCenter.unsubscribe('ts', subscriberTs1);
// 发布事件
eventCenter.publish('ts', '发布了二篇ts文章');
// subscriberTs2 receive: 发布了二篇ts文章
// 取消订阅
eventCenter.unsubscribe('ts');
// 发布事件
eventCenter.publish('ts', '发布了三篇ts文章');