阅读 104

Javascript 浅谈观察者模式和发布-订阅模式

这是我参与8月更文挑战的第6天,活动详情查看:8月更文挑战

一、简介

观察者模式

观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个目标对象,当这个目标对象的状态发生变化时,会通知所有观察者对象,使它们能够自动更新。

观察者模式有两个角色:

  • 目标对象
  • 观察者

观察者模式的简单流程:

  1. 将观察者注册到目标对象的观察者列表中
  2. 目标对象进行 通知(notify) 操作
  3. 目标对象调用观察者的 更新(update) 方法,将通知的信息传递给观察者

联系实际:

报纸期刊的订阅。当你订阅了一份报纸,每天都会有一份最新的报纸送到你手上,有多少人订阅报纸,报社就会发多少份报纸,报社和订报纸的客户就是上面文章开头所说的“一对多”的依赖关系。

具体一般
在报社留下你的电话在目标对象中注册观察者
报社通知订阅者消息在目标对象通过对观察者的引用调用相应的方法

发布-订阅模式

软件架构中,发布-订阅是一种消息范式,消息的发送者(称为发布者)不会将消息直接发送给特定的接收者(称为订阅者)。而是将发布的消息分为不同的类别,无需了解哪些订阅者(如果有的话)可能存在。同样的,订阅者可以表达对一个或多个类别的兴趣,只接收感兴趣的消息,无需了解哪些发布者(如果有的话)存在。

在发布者和订阅者之间存在第三个组件,称为事件中心事件通道,它维持着发布者和订阅者之间的联系,过滤所有发布者传入的消息并相应地分发它们给订阅者。

发布-订阅模式有三个角色:

  • 事件中心
  • 订阅者
  • 发布者

发布-订阅模式的简单流程:

  1. 订阅者需要向事件中心订阅指定的事件
  2. 发布者需要向事件中心发布指定的事件
  3. 事件中心通知订阅者

二、简单实现

观察者模式

class Subject{//目标类
    constructor(){
        this.subjectList = [];              //观察者列表
    }
    add(observer){                          //注册观察者
        this.subjectList.push(observer)
    }
    notify(context){                        //发出通知    
        this.subjectList.forEach(observer => observer.update(context));
    }
    remove(fn){                             //移除观察者
        this.subjectList.splice(this.subjectList.indexOf(fn),1)
    }
}

class Observer{                             //观察者
    update(data){
        console.log(`目标对象发出通知:${data}`)
        /**
         * 相应的业务逻辑
         */
    }
}

var Subject1 = new Subject()//目标1
var Subject2 = new Subject()//目标2

var Observer1 = new Observer()//观察者1
var Observer2 = new Observer()//观察者2

Subject1.add(Observer1);//注册
Subject1.add(Observer2);//注册
Subject2.add(Observer1);//注册

Subject1.notify('test1')//发出通知
Subject2.notify('test2')//发出通知
复制代码

发布-订阅模式

class PubSub {
  constructor() {
    // 维护事件及订阅行为
    this.events = {};
  }
  /**
   * 订阅事件
   */
  subscribe(topic, callback) {
    if (!this.events[topic]) {
      this.events[topic] = [];
    }
    this.events[topic].push(callback);
  }
  /**
   * 发布事件
   * @param {String} topic 事件类型
   * @param {Any} data 发布的内容
   */
  publish(topic, data) {
    // 取出对应主题的回调函数
    const callbacks = this.events[topic];
    if (callbacks) {
      callbacks.forEach(cb => {
        cb(data);
      });
    }
  }
}

const pubSub = new PubSub();
pubSub.subscribe("open", (data) => {
  console.log(data);
});
pubSub.publish("open", { open: true });

复制代码

三、二者的区别

image.png

图片来源于这篇文章

这张非常经典的图非常清晰的描述了观察者模式和发布-订阅模式的区别

  1. 观察者模式中的目标和观察者是直接联系的,而发布-订阅模式中的订阅者和发布者中间是由事件中心来联系的。

    观察者模式:目标和观察者是低耦合的,有很强的依赖关系。

    发布-订阅模式:由于事件中心的存在使得订阅者和发布者是完全解耦的。

  2. 观察者要想订阅目标事件,由于没有事件中心,因此必须将自己添加到目标(Subject) 中进行管理;

    目标在触发事件的时候,也无法将通知操作(notify) 委托给事件中心,因此只能亲自去通知所有的观察者。

  3. 从代码实现的角度,

    观察者模式是面向目标和观察者编程的。

    发布-订阅模式是面向调度中心编程的。

三、注意点

  1. 在给出的实例中,观察者模式中在观察者列表中添加的是观察者对象;而发布-订阅模式在事件中心中添加的是回调函数。个人理解观察者模式中可能更倾向于将通知信息返回到观察者自身,而发布-订阅模式可能更倾向于对相应发布事件的处理(通过回调函数)。

  2. 发布-订阅模式,对于不同的实现,订阅者和发布者不一定要是具体的类(上面有两种实现的方法)。

四、两个小问题?

1. 观察者模式和发布-订阅模式是同一个模式吗?

观察者模式与发布/订阅模式大概就跟番茄与圣女果的关系一样,剪不断理还乱。

有的人认为本质上是同一种模式,但就像上面所描述的,二者还是有一定的区别。

这里引用大佬的一段分析

其实24种基本的设计模式中并没有发布订阅模式,上面也说了,他只是观察者模式的一个别称。

但是经过时间的沉淀,似乎他已经强大了起来,已经独立于观察者模式,成为另外一种不同的设计模式。

或许可以认为————以前订阅发布模式是观察者模式的一种别称,但是发展至今,概念已经有了不少区别。

2. 这个案例是观察者模式还是发布-订阅模式?

// 主题类
class Dep {
  constructor(callback) {
    this.subs = []; // 主题的订阅者
    this.callback = callback;
  }

  // 添加订阅者
  addSub(sub) {
    this.subs.push(sub);
    return this;
  }

  // 主题更新通知---调用订阅者update,通知所有订阅者
  notify() {
    this.subs.forEach(item => item.update(this.callback));
  }
}

// 订阅者
class Sub {
  constructor(val) {
    this.val = val;
  }

  update(callback) {
    this.val = callback(this.val); // 执行订阅主题的函数
    console.log('更新之后:', this.val);
  }
}

// 发布者
class Pub {
  constructor() {
    this.deps = []; // 发布的主题列表
  }

  // 添加主题
  addDep(dep) {
    this.deps.push(dep);
  }

  // 移除主题
  removeDep(dep) {
    let index = this.deps.indexOf(dep);
    if (index !== -1) {
      this.deps.splice(index, 1);
      return true;
    } else {
      return false;
    }
  }

  // 发布主题
  publish(dep) {
    this.deps.forEach(item => item == dep && item.notify());
  }
}

// 新建主题,给主题中加订阅者
let dep1 = new Dep(item => item * item);
dep1.addSub(new Sub(1)).addSub(new Sub(2)).addSub(new Sub(3));

// 新建发布者
let pub = new Pub();
// 添加主题
pub.addDep(dep1);

// 发布者发布,通知这个主题的所有订阅者更新
pub.publish(dep1);

// 输出结果
// 更新之后结果:1
// 更新之后结果:4
// 更新之后结果:9

复制代码

五、参考文章:

订阅发布模式和观察者模式的区别

观察者模式与发布订阅模式的异同

从一道面试题简单谈谈发布订阅和观察者模式

观察者模式与订阅发布模式的区别

JS简单实现发布订阅模式

文章分类
前端
文章标签