发布订阅模式与观察者模式的简单代码实现及解释

86 篇文章 5 订阅
2 篇文章 0 订阅

观察者模式

所谓观察者模式,其实就是为了实现松耦合(loosely coupled)。

用《Head First设计模式》里的气象站为例子,每当气象测量数据有更新,changed()方法就会被调用,于是我们可以在changed()方法里面,更新气象仪器上的数据,比如温度、气压等等。

但是这样写有个问题,就是如果以后我们想在changed()方法被调用时,更新更多的信息,比如说湿度,那就要去修改changed()方法的代码,这就是紧耦合的坏处。

怎么解决呢?使用观察者模式,面向接口编程,实现松耦合。

观察者模式里面,changed()方法所在的实例对象,就是被观察者(Subject,或者叫Observable),它只需维护一套观察者(Observer)的集合,这些Observer实现相同的接口,Subject只需要知道,通知Observer时,需要调用哪个统一方法就好了:

在这里插入图片描述

观察者模式的实现

//观察者模式
//内部基于发布订阅,收集观察者,状态变化后通知

//被观察者
class Subject{
    constructor(name){
        this.name = name
        this.state = '开心'
        this.observers = []
    }
    attach(o){
        this.observers.push(o)
    }
    setState(newState){
        this.state = newState
        this.observers.forEach(o=>o.update(this))
    }
}
//观察者
class Observer{
    constructor(name){
        this.name = name
    }
    update(baby){
        console.log(`当前${this.name}被通知,小宝宝状态${baby.state}`)
    }
}
let baby = new Subject('宝宝')
let father = new Observer('爸爸')
let mother = new Observer('妈妈')

// 被观察者接受观察
baby.attach(father)
baby.attach(mother)
// 被观察者修改状态
baby.setState('不开心')
// 👇观察者被触发

// 当前爸爸被通知,小宝宝状态不开心
// 当前妈妈被通知,小宝宝状态不开心

发布订阅模式

比如小红最近在淘宝网上看上一双鞋子,但是这双鞋卖光了,于是小红订阅上货提醒,等有货的时候就会自动通知,与此同时,小明,小花等也喜欢这双鞋,也订阅上货提醒;等来货的时候就通过依次会通知他们;

在上面的故事中,
小红,小明等属于订阅者,订阅该商品;
卖家属于发布者,当鞋子到了的时候,淘宝会依次通知小明,小红等;
淘宝网属于第三者Broker(调度中心),将两个不相关的人物关联起来。

发布订阅模式的优点

  1. 支持简单的广播通信,当对象状态发生改变时,会自动通知已经订阅过的对象。

  2. 发布者与订阅者耦合性降低,发布者只管发布一条消息出去,它不关心这条消息如何被订阅者使用,同时,订阅者只监听发布者的事件名,只要发布者的事件名不变,它不管发布者如何改变

对于第一点,我们日常工作中也经常使用到,比如我们的ajax请求,请求有成功(success)和失败(error)的回调函数,我们可以订阅ajax的success和error事件。我们并不关心对象在异步运行的状态,我们只关心success的时候或者error的时候我们要做点我们自己的事情就可以了。

发布订阅模式的缺点

  1. 创建订阅者需要消耗一定的时间和内存。
  2. 虽然可以弱化对象之间的联系,如果过度使用的话,反而使代码不好理解及代码不好维护等等。

发布订阅模式的实现

  1. 首先要想好谁是发布者(比如上面的卖家)。
  2. 然后给发布者添加一个缓存列表,用于存放回调函数来通知订阅者(比如上面的买家收藏了卖家的店铺,卖家通过收藏了该店铺的一个列表名单)。
  3. 最后就是发布消息,发布者遍历这个缓存列表,依次触发里面存放的订阅者回调函数。

实现1:

class Center {
  constructor() {
    this.obj = {}
  }
  on(name, fn) {//订阅
    if (!Array.isArray(this.obj[name])) {
      this.obj[name] = []
    }
    this.off(name,fn)//去重
    this.obj[name].push(fn)
  }
  off(name, fn) {//取消订阅
    let tmpObj = this.obj[name]
    for (let i = 0; i < tmpObj.length; i++) {
      if (tmpObj[i] == fn) {
        tmpObj.splice(i, 1)
        break
      }
    }
  }
  emit(parmas) {//发布
    for (let name in this.obj) {
      this.obj[name].forEach((item) => {
        item(parmas)
      })
    }
  }
}
let a = new Center()
// 注意要在外部传入订阅函数,否则对象地址不同,无法匹配
let fna = (parmas) => {
  console.log('用户1可以买' + parmas)
}
let fnb = (parmas) => {
  console.log('用户2可以买' + parmas)
}
a.on('like', fna)//订阅
a.on('like', fnb)//订阅
a.emit('新品')//触发
// 用户1可以买新品
// 用户2可以买新品

a.off('like', fnb)//取消
a.emit('新品')//触发
// 用户1可以买新品

a.on('like', fna)//二次订阅,触发一次
a.emit('新品')//触发 
//用户1可以买新品

实现2

let fs = require('fs')

//第三者Broker
let event = {
    _arr:[],
    on(fn){
        this._arr.push(fn)
    },
    emit(){
        this._arr.forEach(fn=>fn())
    }
}
//订阅
event.on(function(){
    console.log(Object.keys(person))

    if(Object.keys(person).length===3){
        console.log(person)
    }
})
let person = {}
fs.readFile('./name.txt','utf8',(err,data)=>{
    console.log(data)
    person.name = data
    //发布
    event.emit()
})
fs.readFile('./age.txt','utf8',(err,data)=>{
    person.age = data
    event.emit()
})
fs.readFile('./sex.txt','utf8',(err,data)=>{
    person.sex = data
    event.emit()
})

观察者模式VS发布订阅模式

大概很多人都和我一样,觉得发布订阅模式里的Publisher,就是观察者模式里的Subject,而Subscriber,就是Observer。Publisher变化时,就主动去通知Subscriber。

其实并不是。

在发布订阅模式里,发布者,并不会直接通知订阅者,换句话说,发布者和订阅者,彼此互不相识。

互不相识?那他们之间如何交流?

答案是,通过第三者,也就是在消息队列里面,我们常说的经纪人Broker。

在这里插入图片描述

发布者只需告诉Broker,我要发的消息,topic是AAA;
订阅者只需告诉Broker,我要订阅topic是AAA的消息;

于是,当Broker收到发布者发过来消息,并且topic是AAA时,就会把消息推送给订阅了topic是AAA的订阅者。当然也有可能是订阅者自己过来拉取,看具体实现。

发布订阅模式里,发布者和订阅者,不是松耦合,而是完全解耦的。

放一张极简的图,给大家对比一下这两个模式的区别:

在这里插入图片描述

总结

  • 从表面上看:
    观察者模式里,只有两个角色 —— 观察者 + 被观察者
    而发布订阅模式里,却不仅仅只有发布者和订阅者两个角色,还有一个经常被我们忽略的 —— 经纪人Broker
  • 往更深层次讲:
    观察者和被观察者,是松耦合的关系
    发布者和订阅者,则完全不存在耦合
  • 从使用层面上讲:
    观察者模式,多用于单个应用内部
    发布订阅模式,则更多的是一种跨应用的模式(cross-application pattern),比如我们常用的消息中间件

参考文章:
https://hackernoon.com/observer-vs-pub-sub-pattern-50d3b27f838c
https://www.cnblogs.com/tugenhua0707/p/4687947.html
https://zhuanlan.zhihu.com/p/51357583

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值