发布订阅者模式,我们从一条古老的街道说起

1,899 阅读1分钟

1、有一条古老的街道

有一条古老的街道,有一天,开了一个报社,是关于财经的。(定义一个发布者类Publish

我们叫它“财经报社”。(实例化时定义报社名称publisherName

它有一张订阅者登记表。(实例化时定义一个包含订阅者名称的登记表watcherLists

用户登记表可以记录订阅者的名字。(添加订阅者名的方法addWatcher

送报员把报纸送到每一个订阅者手里。(通知订阅者的方法notify

订阅者不订阅时,报社可以移出订阅者的名字。(移出订阅者的方法removeWatcher

报社万一哪天关门时,会清空订阅者列表。(清空订阅者的方法清空发布者列表

上面的每一句话,都代表了一个伪代码,下面具体实现一个发布者类:

class Publish {
    constructor(publisherName) {
         // 发布者名称
        this.publisherName = publisherName;
         // 订阅者列表
        this.watcherLists = []
    }
    // 添加订阅者
    addWatcher(watcher) {
        this.watcherLists.push(watcher)
    }
    // 通知订阅者
    notify() {
        const watcherLists = this.watcherLists.slice()
        for (let i = 0, l = watcherLists.length; i < l; i++) {
            watcherLists[i].update()
        }
    }
    // 移除订阅者
    removeWatcher(watcherName) {
        if (!this.watcherLists.includes(watcherName)) {
            return;
        }
        for (let i = 0; i < this.watcherLists.length; i++) {
            if (this.watcherLists[i].watcherName === watcherName) {
                this.watcherLists[i].removePublishers(this.publisherName)
                this.watcherLists.splice(i, 1)
            }
        }
    }
    // 清空订阅者列表
    clearWatchers() {
        const watcherLists = this.watcherLists.slice()
        for (let i = 0, l = watcherLists.length; i < l; i++) {
            watcherLists[i].removePublishers(this.publisherName)
        }

        this.watcherLists = []
    }
}

财经报馆开业,我们new个报馆实例。

const financialNewspaper = new Publish('财经报社')

2、来了两个财经爱好者

有一天来了两个财经爱好者。(定义一个订阅者类Watcher

订阅者是有名称的。(实例化时定义报社名称watcherName

订阅者订阅的可能不止一家报社。(实例化时定义一个包含报社(发布者)的笔记本publishers

订阅者收到报纸后的行为。(实例时定义定义订阅者的行为(事件)fn

订阅者是通过什么样的方式接收报纸的。(定义接收报纸(发布者发布的消息)的途径,这里统一为信箱方式update

订阅者可以订阅其他报社的报纸。(添加发布者的方式addPublisher

订阅者也可以取消某家报社的报纸。(移除发布者的方式removePublishers

订阅者离开这条街道时,清空报社名称的笔记本。(清空发布者列表clearPublishers

上面的每一句话,都代表了一个伪代码,下面具体实现一个订阅者类:

class Watcher {
    constructor(watcherName, fn) {
        this.watcherName = watcherName; // 订阅者名称
        this.publishers = [] // 发布者列表
        this.fn = fn // 监听者收到消息后的反应(事件)
    }
    // 更新自身事件(行为)
    update() {
        this.fn();
    }
    // 添加发布者
    addPublisher(publisher) {
        this.publishers.push(publisher)
    }
    // 移除发布者
    removePublishers(publisherName) {
        if (!this.publishers.includes(publisherName)) {
            return;
        }
        for (let i = 0; i < this.publishers.length; i++) {
            if (this.publishers[i].publisherName === publisherName) {
                this.publishers[i].removeWatcher(this.watcherName) // 通知发布者删除订阅者
                this.publishers.splice(index, 1) // 从发布者列表中清除发布者
            }
        }
    }
    // 清空发布者列表
    clearPublishers() {
        const publishers = this.publishers.slice()
        for (let i = 0, l = publishers.length; i < l; i++) {
            publishers[i].removeWatcher(this.watcherName)
        }

        this.publishers = []
    }
}

关于订阅者,我们new两个订阅者实例。

const watcherA = new Watcher('watcherA', function () {
    console.log('喝着茶,看着报纸')
})
// 定义订阅者B
const watcherB = new Watcher('watcherB', function () {
    console.log('大清早,晨读报纸')
})

财经报刊添加了两个订阅者watcherAwatcherB

financialNewspaper.addWatcher(watcherA)
financialNewspaper.addWatcher(watcherB)
// 可以打印发布者和发布者收集的订阅者列表
console.log(financialNewspaper, financialNewspaper.watcherLists);

两个细心的订阅者把财经报刊记录在了小本本上。

watcherA.addPublisher(financialNewspaper);
watcherB.addPublisher(financialNewspaper);
// 可以打印订阅者和订阅者订阅的报刊种类
console.log(watcherA, watcherA.publishers);
console.log(watcherB, watcherB.publishers);

3、订阅者收到报纸

第二天,送报员就把报纸投进了门口邮箱(相当于财经报刊进行了消息发布)

financialNewspaper.notify()
// watcherA和watcherB收到报纸(消息)后,就触发了他们的行为
// watcherA:'喝着茶,看着报纸'
// watcherB:'大清早,晨读报纸'

4、财经报社又来了个订阅者

有一天财经报社来了个watcherC,也订阅了报刊。

我们再new个订阅者watcherC:

const watcherC = new Watcher('watcherC', function () {
    console.log('大晚上,熬夜看报纸')
})

报社把订阅者watcherC记录在了登记表上。

financialNewspaper.addWatcher(watcherC)

同样细心的订阅者watcherC也把财经报社记录在了小本本上。

watcherC.addPublisher(financialNewspaper);

5、街道上又开了家体育类报社

有一天街道上又开了个体育报社。

我们先new一个体育报社。

const sportsNewspaper = new Publish('体育报社')

watcherAwatcherC也是体育爱好者,所以订阅了体育报刊。

体育报社需要登记两个订阅者的姓名。

sportsNewspaper.addWatcher(watcherA)
sportsNewspaper.addWatcher(watcherC)

这两订阅者,又各自把体育报社记录在了小本本上。

watcherA.addPublisher(sportsNewspaper);
watcherC.addPublisher(sportsNewspaper);

6、有订阅者取消体育报刊的报纸

订阅者watcherC本来不喜欢运动,起初订阅体育报刊纯粹为了凑热闹,三天的劲头已过,他决定取消体育报刊的报纸。

watcherC.removePublishers('sportsNewspaper')

7、有订阅者要离开这条街道

有一天,watcherA要出国留学,所以就从小本本上划掉了记录的报刊名称,并且通知报社取消报纸的订阅,第二天,送报员就没再给watherA送报纸。

watcherA.clearPublishers()

这里watcherC清掉小本本上名称的同时,也会通知到报社,体育报社和财经报社同样会在等级表上清除watcherC的名称。

clearPublishers() {
    const publishers = this.publishers.slice()
    for (let i = 0, l = publishers.length; i < l; i++) {
        publishers[i].removeWatcher(this.watcherName)
    }

    this.publishers = []
}

8、有报社要关门

岁月如梭,多年过去啦。

随着移动互联网的兴起,纸媒受到影响,这条街道的财经报社决定关门。

financialNewspaper.clearWatchers()

第二天就不再给登记表上的订阅者送报啦,订阅者收到消息后,从小本本上划掉了财经类报刊的名字。

clearWatchers() {
    const watcherLists = this.watcherLists.slice()
    for (let i = 0, l = watcherLists.length; i < l; i++) {
        watcherLists[i].removePublishers(this.publisherName)
    }

    this.watcherLists = []
}

这里描述了发布者的产生、订阅者的产生、发布者发布消息的方式、订阅者接受消息的途径、订阅者接收到消息的行为、发布者的新增、订阅者的新增、发布者的离开和订阅者的离开等关系和逻辑。代码具体的执行结果,还需要学友自行运行验证。

总结

发布订阅者模式又叫观察者模式,它定义了对象间的一种一对多的关系。这种关系,既指一个发布者可以对应多个订阅者,又可以指一个订阅者也订阅多个发布者的消息。

写在最后

如有错误烦请贵手留言,如果帮助请点赞支持❤。