前端设计模式(六):观察者模式与发布订阅模式
前端设计模式(六):观察者模式与发布订阅模式
参考文章:
- 观察者模式/发布订阅模式的应用[https://juejin.cn/post/7069042125492453413]
- 图解设计模式之观察者模式(TypeScript)[https://juejin.cn/post/6862112623417098248]
观察者模式与发布订阅模式虽然相似,但是还是存在一些差异:
-
观察者模式(Observer)有两个对象,观察者对象(Observer)和目标对象(Subject),观察者观察或者说是订阅(Subscribe)目标,当目标发生变化,就通知(Fire Event)观察者去做更新的操作
-
发布订阅模式(Publish/Subscribe)在这两个对象之间多了一个事件通道(Event Channel)作为中间过渡,目标对象发生了变化,不直接通知观察者,而是通过事件通道去通知观察者做更新,因此观察者也只需要订阅事件通道的消息即可
一、观察者模式:
- 目标对象(Subject):也就是被观察者,维护一个观察者的列表,可以用来给已经观察的对象发布消息。这个Subject可以包含添加观察者的方法、移除观察者的方法、通知方法(也就是自身更新需要通知已经观察的对象)
- 观察者(Observer),观察目标对象的类,用来观察主体对象的变化,主要有接收通知的方法,自定义的业务方法等等
下面来看一个最简单的观察者例子:
/**
* 观察者:用来观察主体对象的变化
*/
class Observer {
constructor(name) {
this.name = name;
}
update(params){
console.log(this.name + '接收到了:', params)
this.myFunction();
}
myFunction() {
console.log(this.name + '自定义的方法...')
}
}
/**
* 主体者:用来维护观察者列表,批量发布消息
* 包含:更新通知方法、添加订阅方法
*/
class Subject {
constructor() {
this.observerList = [];
}
// 添加订阅
add(oberver){
this.observerList.push(oberver);
}
// 发送通知
notify(params) {
this.observerList.forEach(oberver => oberver.update(params))
}
}
const subject = new Subject();
const ob_1 = new Observer('观察者1');
const ob_2 = new Observer('观察者2');
subject.add(ob_1);
subject.add(ob_2);
subject.notify({ test: '这是测试参数' });
经过运行,控制台输出
观察者1接收到了: { test: '这是测试参数' }
观察者1自定义的方法...
观察者2接收到了: { test: '这是测试参数' }
观察者2自定义的方法...
基于TypeScript语法的实现,思路其实是与上面一样的:
// ObserverPattern.ts
interface Subject {
addObserver: (observer: Observer) => void;
deleteObserver: (observer: Observer) => void;
notifyObservers: () => void;
}
interface Observer {
notify: () => void;
}
class ConcreteSubject implements Subject {
private observers: Observer[] = [];
public addObserver(observer: Observer): void {
console.log(observer, " is pushed~~");
this.observers.push(observer);
}
public deleteObserver(observer: Observer): void {
console.log(observer, " have deleted~~");
const idx: number = this.observers.indexOf(observer);
idx && this.observers.splice(idx, 1);
}
public notifyObservers(): void {
console.log("notify all the observers ", this.observers);
this.observers.forEach(observer => {
// 调用 notify 方法时可以携带指定参数
observer.notify();
});
}
}
class ConcreteObserver implements Observer {
constructor(private name: string) {}
notify(): void {
// 可以处理其他逻辑
console.log(`${this.name} has been notified.`);
}
}
function useObserver(): void {
const subject: Subject = new ConcreteSubject();
const Leo = new ConcreteObserver("Leo");
const Robin = new ConcreteObserver("Robin");
const Pual = new ConcreteObserver("Pual");
const Lisa = new ConcreteObserver("Lisa");
subject.addObserver(Leo);
subject.addObserver(Robin);
subject.addObserver(Pual);
subject.addObserver(Lisa);
subject.notifyObservers();
subject.deleteObserver(Pual);
subject.deleteObserver(Lisa);
subject.notifyObservers();
}
useObserver();
二、发布订阅模式
发布订阅模式其实与观察者模式相比,多了一层中间者,这个中间者就是发布者与订阅者之间的联系的桥梁
- 消息中心:负责存储消息与订阅者的对应关系,有消息触发时,负责通知订阅者
- 订阅者:去消息中心订阅自己感兴趣的消息
- 发布者:满足条件时,通过消息中心发布消息
下面来手动实现一个简单的Event
/**
* @Author: fanx
* @Date: 2022年06月28日 15:52
* @Description: file content
*/
class EventEmitter {
constructor() {
this.eventMap = new Map();
}
// 订阅
on(eventName, callback){
// 如果有多个订阅者订阅了,依次保存
if(this.eventMap.has(eventName)){
this.eventMap.get(eventName).push(callback);
}else {
// 没人订阅,直接添加
this.eventMap.set(eventName,[callback]);
}
}
// 发布
emit(eventName, ...args) {
// 取出对应事件名称的所有订阅者
const eventList = this.eventMap.get(eventName);
if(eventList && eventList.length){
eventList.forEach(callback => callback.call(this, ...args))
}else {
console.log('未找到对应事件')
}
}
off(eventName, callback){
const eventList = this.eventMap.get(eventName);
if(eventList && eventList.length){
const filterEventList = eventList.filter(cb => cb === callback);
this.eventMap.set(eventName, filterEventList);
}
}
}
const emitter = new EventEmitter();
// 监听事件
const callback_1 = (...args) => {
console.log('我是监听事件1',...args);
};
const callback_2 = (...args) => {
console.log('我是监听事件2',...args);
};
emitter.on('test',callback_1);
emitter.on('test',callback_2);
setTimeout(() => {
emitter.emit('test',1,2,3);
},2000)
setTimeout(() => {
emitter.off('test');
console.log('已经移除test')
emitter.emit('test',1,2,3);
},2500)
执行结果
我是监听事件1 1 2 3
我是监听事件2 1 2 3
已经移除test
未找到对应事件
三、总结
发布—订阅模式的优点非常明显,一为时间上的解耦,二为对象之间的解耦。它的应用非常广泛,既可以用在异步编程中,也可以帮助我们完成更松耦合的代码编写。发布—订阅模式还可以用来帮助实现一些别的设计模式,比如中介者模式。从架构上来看,无论是 MVC 还是 MVVM,都少不了发布—订阅模式的参与,而JavaScript 本身也是一门基于事件驱动的语言。
当然,发布—订阅模式也不是完全没有缺点。创建订阅者本身要消耗一定的时间和内存,而且当你订阅一个消息后,也许此消息最后都未发生,但这个订阅者会始终存在于内存中。另外,发布—订阅模式虽然可以弱化对象之间的联系,但如果过度使用的话,对象和对象之间的必要联系也将被深埋在背后,会导致程序难以跟踪维护和理解。特别是有多个发布者和订阅者嵌套到一起的时候,要跟踪一个 bug 不是件轻松的事情。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律