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简单实现发布订阅模式

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享