前言

观察者模式和发布订阅模式作为日常开发中经常使用到的模式,我一直不能做到很好的区分。最近在看Vue的源码,里面设计到了观察者模式,比较感兴趣,就去学习了下,这里做个总结吧。

一、发布订阅模式

什么是发布订阅模式?

基于一个事件中心,接收通知的对象是订阅者,需要先订阅某个事件,触发事件的对象是发布者,发布者通过触发事件,通知各个订阅者。

举例:比如平时订阅的微信公众号,这里就涉及到两个角色:公众号(事件中心)和订阅了公众号的用户(订阅者)。当公众号的作者发布了文章之后,订阅公众号的用户就会收到消息,这里又涉及到了一个角色:公众号的作者(发布者).

应用场景

vue中的事件总线就是使用的发布订阅模式;它使用 $emit$on 进行兄弟组件通信,进行参数传递。

手动实现vue中的发布订阅模式:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Vue 中发布订阅模式</title>
</head>
<body>
  <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
  <script>  
    // Vue 的实例,创建事件总线
    let bus = new Vue()
    // 订阅事件
    bus.$on('eventName1', val => {
      console.log('eventName1---->', val)
    })
    bus.$on('eventName2', val => {
      console.log('eventName2---->', val)
    })

    // 发布事件
    bus.$emit('eventName1', 'eventName1')
    bus.$emit('eventName2', 'eventName2')
  }
  </script>
</body>
</html>

打印结果:

在这里插入图片描述

在上述代码中,bus 就是我们创建的 事件总线,它是一个 Vue 实例。我们可以在不同的组件中引入这个实例,并使用 $on 方法来监听事件,使用 $emit 方法来触发事件。通过共享同一个事件总线实例,不同组件之间可以通过事件进行通信,实现解耦。

需要注意的是,使用 事件总线模式 时要确保在适当的时候 销毁 事件总线,以避免出现 内存泄漏 的问题。可以在组件的生命周期钩子中销毁事件总线:

beforeDestroy() {
  bus.$off();
}

二、观察者模式

1)什么是观察者模式?

目标者对象 和 观察者对象 有相互依赖的关系,观察者对某个对象的状态进行观察,如果对象的状态发生改变,就会通知所有依赖这个对象的观察者进行更新操作。

观察者模式相比发布订阅模式少了个 事件中心

  • 目标者对象【Subject】:是被观察的对象,它维护一组观察者,并提供用于 添加、删除和通知 观察者的方法。
  • 观察者对象【Observe】:接收 Subject 状态变更通知并处理。
  • 目标者对象【Subject】状态变更时,通知所有观察者对象【Observe】进行更新操作。

2)应用场景

观察者模式在Vue中应用场景:数据响应式变化

在上一篇 Vue源码学习 - 数据响应式原理 文章中已经了解到,每个响应式属性都有 一个 dep实例 ,dep存放了依赖这个属性的 watcher(watcher是观测数据变化的函数),如果数据发生变化,dep 就会通知所有依赖它的 观察者watcher 去调用更新方法。因此,观察者需要被目标对象收集,目的是通知依赖它的所有观察者
为什么watcher中也要存放dep呢?原因是因为当前正在执行的 watcher 需要知道此时是哪个 dep 通知了自己

3)vue中的观察者模式

观察者(订阅者) - Watcher

  • update(): 当事件发生时,具体要做的事情。

目标者(发布者) - Dep

  • subs数组:存储所有的观察者。
  • addSub():添加观察者。
  • removeSub():移除观察者。
  • notify():通知观察者; 当事件发生后调用所有观察者的update()。

没有事件中心

手动实现观察者模式

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>观察者模式</title>
</head>
<body>
  <script>
    // 目标者(发布者)
    class Dep {
      constructor () {
        // 记录所有的订阅者
        this.subs = []
      }
      // 添加订阅者
      addSub (sub) {
        if (sub && sub.update) {
          this.subs.push(sub)
        }
      }
      // 发布通知
      notify () {
        this.subs.forEach(sub => {
          sub.update()
        })
      }
    }
    
    // 观察者(订阅者)
    class Watcher {
      update () {
        console.log('update')
      }
    }
    
    //创建实例化对象 测试一下
    let dep = new Dep()
    let watcher = new Watcher()
    let watcher1 = new Watcher()
    // 添加订阅者
    dep.addSub(watcher)
    dep.addSub(watcher1)
    // 开启通知
    dep.notify()
    // 执行结果 update --->
  </script>
</body>
</html>

打印结果:触发两次更新通知。

在这里插入图片描述

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

1)从组成分析

  • 观察者模式里,只有两个角色:观察者目标者(也可以叫被观察者)。其中 被观察者 是重点。
  • 发布订阅模式里,不仅仅只有 发布者订阅者,还有一个 事件中心。其中 事件中心 是重点。
观察者模式发布订阅模式
2个角色3个角色
重点是 被观察者(目标者)重点是 事件中心

2)从关系上分析

  • 观察者和目标者,是松耦合的关系
  • 发布者和订阅者,则完全不存在耦合

3)从使用角度分析

  • 观察者模式,多用于 单个应用内部 (Vue中的数据响应式变化就是观察者模式)
  • 发布订阅模式,更多用于 跨应用的模式(比如我们常用的 消息中间件

可参考:
发布订阅模式和观察者模式

Logo

前往低代码交流专区

更多推荐