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

2,277 阅读5分钟

文章已同步至【个人博客】,欢迎访问【我的主页】😃
文章地址:blog.fanjunyang.zone/archives/js…

有人说这两个模式其实是一个模式。这句话一半对,一半错吧。它们有类似的地方,不过也不能说完全一致。先来一张图,这张图解释了观察者模式和发布订阅模式在流程上的一些区别:

阐述二者的模型:

  • 观察者模式里,观察者(Observer)直接订阅(subscribe)主题(Subject),而当主题被激活的时候,会触发(fire)观察者里的事件。

  • 发布订阅模式里,订阅者(Subscriber)通过监听(on)事件总线(Event Bus)里的事件,当事件总线里的事件被触发(emit)的时候,订阅者将会执行相应的操作。而这里需要注意的是,事件总线里的事件是通过发布者(Publisher)进行发布(publish)和 通知事件总线 触发 的。

观察者模式

如上图所示,既然发布/订阅是比观察者模式多了一些东西,那么我们就先来看观察者模式。

观察者模式 :定义对象间一种一对多的依赖关系

简单的就是说:A 对象依赖 B 对象的某个状态变化来执行一些逻辑,那么 A 就需要观察 B 的某个状态,B 会在这个状态发生改变时通知 A,并且可以把描述这个状态的数据给 A

举个例子:

假设你是个学渣,你同桌是个学霸,今天要考试了,卷子发下来,你几乎一道题都不会做,于是你对同桌说:你做完了给我抄一下,你同桌做完了,拿胳膊肘捅你一下,把试卷往你这边挪。

你是观察者,你同桌是被观察者,你观察的状态是你同桌做完了,你同桌的试卷是你要的数据,这个状态发生的时候你要处理的逻辑是抄答案。

抄过作业的都知道,你不可能一直盯着你同桌或者一直问他写完没吧?在程序中,如果对象 A 一直不停的去获取 B 的某个状态来看有没有发生改变,这不是观察者模式

什么是一对多的依赖关系?

你同桌是学霸,你同桌前后左右都跟你一样是学渣,你们都得抄他的,不然全不及格。就是这样。

在程序中,就是一个按钮被点击了,程序打开一个页面。所谓的监听这个按钮的点击事件就是一种观察者模式

撸段代码

//被观察者
class Subject{
    constructor(){
        this.arr = []   //存储观察者
        this.state = "开心..."  //存储被观察者状态
    }
    attach(unit){
        this.arr.push(unit)
    }
    //改变状态
    setState(newState){
        this.state = newState
        this.arr.forEach(unit=>unit.update(newState))
    }
}
//观察者
class Observer{
    constructor(name){
        this.name = name
    }
    update(newState){
        console.log(this.name,"状态:",newState);
    }
}
let s = new Subject("宝宝")
let o1 = new Observer("我")
let o2 = new Observer("你")

s.attach(o1)
s.attach(o2)
console.log(s.state);   //开心...
s.setState("不开心...");    //我 状态: 不开心...   你 状态: 不开心...
console.log(s.state);   //不开心...

发布订阅模式

在发布订阅模式中:消息的发送方,叫做发布者(publishers),消息不会直接发送给特定的接收者,叫做订阅者。

意思就是发布者和订阅者不知道对方的存在。需要一个第三方组件,叫做中间件,它将订阅者和发布者串联起来。

举个例子:

你在淘宝上看中了一个电脑,然后你找了个代购,你跟他说如果找到这个电脑就买给你,这个代购路子野有很多渠道,电脑到货后这些渠道商会告诉这个代购,电脑到货了,然后代购买下来发给你

这个才是 发布订阅模式

而观察者模式呢?

你在淘宝上看上了一个电脑,但是没货了,于是你给店家说到货了告诉你,店家说好的,到货了告诉你。

这个是观察者模式

在程序中,比方说一个应用区分登录状态和未登录状态的UI,很多不同的页面都可以实现登录的逻辑,在登录成功后,所有当前被打开的页面都需要更新UI。这种情况就可以使用发布/订阅模式,在某个页面登录成功后发布登录成功的事件,有中间件将这个事件分发给其他页面更新UI,
注:其他页面关心的只是登录状态改变这件事,而不关心用户具体在哪个页面登录的

撸代码:

let e = {
    arr:[],
    on(fn){
        this.arr.push(fn)
    },
    emit(){
        this.arr.forEach(fn=>fn())
    }
}
e.on(()=>{
    console.log("哈哈 1");
})
e.on(()=>{
    console.log("哈哈 2");
})

//调一次打印一次
e.emit()    //哈哈 1  哈哈 2  
e.emit()    //哈哈 1  哈哈 2 

区别:

这两种模式区别可以简单归结为是观察状态还是事件。

观察者模式中:

  • 状态发布者维护了一个观察者的列表,明确的知道有哪些观察者存在,将状态变化直接通知给观察者
  • 状态的观察者也明确的知道自己观察的状态是描述的哪一个对象
  • 甚至需要这种相互知道的关系来处理逻辑(比如需要明确知道哪一个按钮被点击,处理对应的逻辑)

发布订阅模式:

  • 事件的发布者只发布事件,不关心这个事件被谁获取了,通常将事件发给一个中间件,由中间件再去分发事件
  • 事件的订阅者只关心事件本身,不关心这个事件是谁发布的,通常在中间件中去注册观察某个事件
  • 中间件中去维护事件类别对应的订阅者列表,当收到事件后,去对应列表中通知订阅者们

应用场景

按如下原则判断:

是否观察的是状态(明确知道状态源)

如果被观察和观察者双方都明确知道对方,那就观察者模式,否则就是发布订阅模式

一对一或者一对多的关系

这个事件或者状态只有一个发布者,两种都可以用,再参考第一条

多对多的关系

首先所谓多对多的关系基本就可以确定传递的是事件,而不是状态,因为不同对象不应该发布相同的状态,所以不要犹豫就选发布/订阅


#_#