通俗易懂的了解观察者模式/发布订阅模式

1,106 阅读8分钟

《局中人》这部电视剧相信看过的人不少,我们以《局中人》为例子,讲解观察者模式和发布订阅模式。 观察者模式和发布订阅模式如果只看概念,可能会觉得比较晦涩难懂,因此,本文将尽可能讲的通俗易懂一点。

观察者模式

什么是观察者模式? 按照概念来讲,观察者模式就是一个对象维持了多个依赖于它的对象,当这个对象的相关状态发生改变时,依赖它的多个对象就会进行更新,得到这些状态。

局中人中,中统局处长沈林和其弟弟沈放是亲兄弟,正因为如此,沈林想要迫切的知道沈放到底是不是地下党。 因此,沈林派遣了情报人员对沈放进行全天候跟踪。
那么,以观察者模式来对号入座的话,情报人员属于观察者,沈放是那个被观察的对象。

SubType类,也就代指被跟踪监视的沈放

class SubType {
    constructor(name){
        this.name = name // 
        this.state = null // 被观察者的初始
        this.observers = [] // 观察者集合,即一个对象可以有多个观察者,这里可以理解为有多少情报人员盯着沈放
    }

    // 添加观察者,即随时可能有其他情报人员加入监视沈放的行动中
    addObserver(...args){
         this.observers.concat(...args)
    }

    // 移除某个观察者,即某个情报人员有了其他任务,从监视行动中撤出来
    removeObserver(observer) {
        if (!observer) {
            return
        }
        let index = this.observers.findIndex(item => item === observer)
        this.observers.splice(index, 1)
    }

    // 被观察对象的状态改变,比如沈放早上从家里出来后,驱车前往中统局上班,中午去喜乐门吃饭,晚上去喜乐门跳舞
    setState(action){
        // 当沈放有什么新动作后,会自动调用getInfo方法,情报人员会通过这个方法获取到沈放的最新行踪
        this.state = action 
        this.getInfo(action)
    }

    // 所有监视沈放的情报人员都会得到沈放的最新行踪
    getInfo(action){
        this.observers.forEach(item => {
            item.update((name) => {
                console.log(`${name}监视情况:目标人物-${this.name} -> ${action}`)
            })
        })
    }

}

接下来看看观察者对象:

观察者类

class Observer {
    constructor(name){
        this.name = name // 情报人员的名称或者代号
    }

    update(callback){
        callback?.(this.name)
    }
}

派遣了三位情报人员

let P1 = new Observer('情报人员1')
let P2 = new Observer('情报人员2')
let P3 = new Observer('情报人员3')

开始观察目标任务-沈放

let info = new SubType('沈放')

// 三位情报人员就位
info.addObserver(P1)
info.addObserver(P2)
info.addObserver(P3)

接下来,沈放的一些行动会被情报人员接收到

info.setState('早上出门直接去了中统局')
info.setState('中午去和银行行长的女儿吃了顿饭')
info.setState('晚上去了喜乐门喝酒,彻夜未归')

看下控制台的打印:

情报人员1监视情况:目标人物-沈放 -> 早上出门直接去了中统局
情报人员2监视情况:目标人物-沈放 -> 早上出门直接去了中统局
情报人员3监视情况:目标人物-沈放 -> 早上出门直接去了中统局

情报人员1监视情况:目标人物-沈放 -> 中午去和银行行长的女儿吃了顿饭
情报人员2监视情况:目标人物-沈放 -> 中午去和银行行长的女儿吃了顿饭
情报人员3监视情况:目标人物-沈放 -> 中午去和银行行长的女儿吃了顿饭
 
情报人员1监视情况:目标人物-沈放 -> 晚上去了喜乐门喝酒,彻夜未归
情报人员2监视情况:目标人物-沈放 -> 晚上去了喜乐门喝酒,彻夜未归
情报人员3监视情况:目标人物-沈放 -> 晚上去了喜乐门喝酒,彻夜未归

沈林了解了情况后说道:辛苦兄弟们了,不用所有人都盯着,你们轮班吧,其他人去休息休息,放松一下。 于是,哥几个商量了下,1号先撤

info.removeObserver(P1)
info.setState('出门直接去了中统局')

此时控制台的打印为:

情报人员2监视情况:目标人物-沈放 -> 中午去和银行行长的女儿吃了顿饭
情报人员3监视情况:目标人物-沈放 -> 中午去和银行行长的女儿吃了顿饭

发布订阅模式

发布订阅模式是指:一个对象想要基于某个主题来订阅一个主题事件,于是发布者发布了一个基于订阅主题的主题事件来通知每个订阅对象接收消息。

发布订阅模式和观察模式还真不太一样。
不一样的地方在于,发布订阅模式比观察者模式多了一个调度中心,所有的发布主题事件,订阅主题事件都是通过调度中心完成的。

我们还是以上面的例子来看,换成发布订阅者模式之后,多出了一个调度中心,把情报中心想象成调度中心。

情报人员收到了来自沈林对沈放的监视命令,开始对沈放进行24小时跟踪监视。


// 调度中心,这里指的是跟踪监视沈放的情报人员观察地
class Event {
    constructor(){
        this.listners = {} // 情报人员收到的所有命令
    }

    // 订阅方法,接收一个主题类型和收到主题类型后做出的事情
    // 沈林收到情报人员的反馈后决定做些什么
    subscribe(type, fn){
        // 当第一次订阅该主题事件时,给该主题事件创建一个数组,因为可以有多个订阅者订阅这一事件
        // 沈林和中统局局长都向情报中心发布了监视沈放的命令
        if(!this.listners[type]) {
            this.listners[type] = []
        }

        // 将订阅事件的回调方法放置进数组
        this.listners[type].push(fn)

        // 取消订阅事件(撤销对沈放的监视)
        return (type, fn) => {
            // 如果没有传要取消订阅的主题事件,就返回
            if(!type) {
                return false
            }

            // 如果没有传某个订阅事件下的fn,就将该主题事件整个取消掉
            if(!fn) {
                delete this.listners[type]
            }

            // 否则,就遍历该事件下的所有fn,将匹配到的从数组里删除
            this.listners = this.listners[type].filter(item => item !== fn)
        }
    }

    // 发布方法,基于接收到的主题事件,发布该主题事件
    publish(type, ...args) {
        let self = this
        // 如果在发布的时候,没有订阅过这个主题事件,则不会发布
        // 沈林问情报人员喜乐门最近有没有什么可疑人员,情报人员回应没有接到过这个命令,没办法给出答复
        if(!this.listners[type]) {
            return false
        }

        this.listners[type].forEach(item => {
            item.apply(self, args)
        })
    }
}  

先实例化出一个对象

let event = new Event()

沈林下达了监视沈放的命令

event.subscribe('沈林下达的命令:监视沈放', (content) => {
    console.log(content)
})

中统局局长下达了监视沈放的命令

event.subscribe('中统局局长下达的命令:监视沈放', (content) => {
    console.log(content)
})

发布主题事件(情报人员向沈林汇报情况)

event.publish('沈林下达的命令:监视沈放', '报告处长,沈林今天没什么异常举动,跟以前一样;不过叶局长也向我们下达了一个和您一样的命令,都是监视沈放')

发布主题事件(情报人员向叶局长汇报情况)

event.publish('中统局局长下达的命令:监视沈放', '报告局长,沈林跟之前一样,没什么异常举动,就是上班,去喜乐门喝酒,回家')

沈林挂掉电话后,狐疑了起来:局长明知道我在监视沈放,为什么还要刻意的再下达一次命令呢?难道还暗中让他们监视其他人?
为了验证自己的想法,沈林决定旁敲侧击的来了解一下,于是便打电话问情报人员:喜乐门今天有可疑人物吗?

情报人员回答道:这个。。。。。。 处长,我们没收到您下达的监视喜乐门的命令啊,我们的注意力都集中在沈放身上了。

event.publish('喜乐门发生了什么') // false 由于没有下达命令,因此无法得到回复

之后,对沈放的监视被沈放识破,沈放为了解除这个让自己寝食难安的监视,便将此事添油加醋的告诉了军统一处的罗处长,罗处长听了之后勃然大怒,询问中统几个意思。
迫于这种压力,中统打算撤销对沈放的监视。

let unsubscribe1 = event.subscribe('沈林下达的命令:监视沈放', (content) => {
    console.log(content)
})

let unsubscribe2 = event.subscribe('中统局局长下达的命令:监视沈放', (content) => {
    console.log(content)
})

// 情报人员放弃了对沈放的监视
unsubscribe1('沈林下达的命令:监视沈放')
unsubscribe2('中统局局长下达的命令:监视沈放')

event.publish('沈林下达的命令:监视沈放', '报告局长,沈林跟之前一样,没什么异常举动,就是上班,去喜乐门喝酒,回家') // false 监视命令已解除
event.publish('中统局局长下达的命令:监视沈放', '报告局长,沈林跟之前一样,没什么异常举动,就是上班,去喜乐门喝酒,回家') // false 监视命令已解除