前端设计模式(六):观察者模式与发布订阅模式

前端设计模式(六):观察者模式与发布订阅模式


参考文章:

观察者模式与发布订阅模式虽然相似,但是还是存在一些差异:

  • 观察者模式(Observer)有两个对象,观察者对象(Observer)和目标对象(Subject),观察者观察或者说是订阅(Subscribe)目标,当目标发生变化,就通知(Fire Event)观察者去做更新的操作

  • 发布订阅模式(Publish/Subscribe)在这两个对象之间多了一个事件通道(Event Channel)作为中间过渡,目标对象发生了变化,不直接通知观察者,而是通过事件通道去通知观察者做更新,因此观察者也只需要订阅事件通道的消息即可

一、观察者模式:
  1. 目标对象(Subject):也就是被观察者,维护一个观察者的列表,可以用来给已经观察的对象发布消息。这个Subject可以包含添加观察者的方法、移除观察者的方法、通知方法(也就是自身更新需要通知已经观察的对象)
  2. 观察者(Observer),观察目标对象的类,用来观察主体对象的变化,主要有接收通知的方法,自定义的业务方法等等

image

下面来看一个最简单的观察者例子:


/**
 * 观察者:用来观察主体对象的变化
 */
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();
二、发布订阅模式

发布订阅模式其实与观察者模式相比,多了一层中间者,这个中间者就是发布者与订阅者之间的联系的桥梁

image

  1. 消息中心:负责存储消息与订阅者的对应关系,有消息触发时,负责通知订阅者
  2. 订阅者:去消息中心订阅自己感兴趣的消息
  3. 发布者:满足条件时,通过消息中心发布消息

下面来手动实现一个简单的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 不是件轻松的事情。

posted @   SuanYunyan  阅读(425)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律
点击右上角即可分享
微信分享提示