详解并手动实现观察者模式、发布订阅模式

1:背景

设计模式的定义是:在面向对象软件设计过程中针对特定问题的简洁而优雅的解决方案。通俗一点说,设计模式是在某种场合下对某个问题的一种解决方案。如果再通俗一点说,设计模式就是给面向对象软件开发中的一些好的设计取个名字。

这些“好的设计”并不是谁发明的,而是早已存在于软件开发中。一个稍有经验的程序员也许在不知不觉中数次使用过这些设计模式。GoF(Gang of Four--四人组,《设计模式》几位作者)最大的功绩是把这些“好的设计”从浩瀚的面向对象世界中挑选出来,并且给予它们一个好听又好记的名字。

设计模式并不直接用来完成代码的编写,而是描述在各种不同情况下,要怎么解决问题的一种方案,他不是一个死的机制,他是一种思想,一种写代码的形式。每种语言对于各种设计模式都有他们自己的实现方式,对于某些设计模式来说,可能在某些语言下并不适用,比如工厂方法模式对于javascript。模式应该用在正确的地方。而哪些才算正确的地方,只有在我们深刻理解了模式的意图之后,再结合项目的实际场景才会知道。。

设计模式的社区一直在发展。GoF在1995年提出了23种设计模式,但模式不仅仅局限于这23种,后面增加到了24种。在这20多年的时间里,也许有更多的模式已经被人发现并总结了出来,比如一些JavaScript 图书中会提到模块模式、沙箱模式等。这些“模式”能否被世人公认并流传下来,还有待时间验证。

 

2.详解

2.1 观察者模式(Observer Pattern)

观察者模式定义了对象间的一种一对多的关系,当一个对象的状态发生改变时,所有依赖他的对象都将得到通知,并自动更新。观察者模式属于行为型模式,行为型模式关注的是对象间的通讯,观察者模式就是观察者和被观察者之间的通讯。

观察者模式有一个别名叫“发布-订阅”模式,订阅者和订阅目标是联系在一起的,当订阅目标发生改变时,逐个通知订阅者。我们可以用报纸期刊的订阅来形象的说明,当你订阅了一份报纸,每天都会有一份最新的报纸送到你手上,有多少人订阅报纸,报社就会发多少份报纸,报社和订报纸的客户就是上面文章开头所说的“一对多”的依赖关系。

 

2.2 发布订阅模式(Pub-Sub Pattern)

其实24种基本的设计模式中并没有发布订阅模式,上面也说了,他只是观察者模式的一个别称。

但是经过时间的沉淀,似乎他已经强大了起来,已经独立于观察者模式,成为另外一种不同的设计模式。

在现在的发布订阅模式中,称为发布者的消息发送者不会将消息直接发送给订阅者,这意味着发布者和订阅者不知道彼此的存在。在发布者和订阅者之间存在第三个组件,称为消息代理或调度中心或中间件,它维持着发布者和订阅者之间的联系,过滤所有发布者传入的消息并相应地分发它们给订阅者。

举一个例子,你在微博上关注了A,同时其他很多人也关注了A,那么当A发布动态的时候,微博就会为你们推送这条动态。A就是发布者,你是订阅者,微博就是调度中心,你和A是没有直接的消息往来的,全是通过微博来协调的(你的关注,A的发布动态)。

 

2.3 观察者模式和发布订阅模式有什么区别

这两种模式的实现结构如下:

观察者模式:观察者(subscriber)直接订阅(subscribe)主题(subject),而当主题被激活时,直接触发(trigger)观察者里面的事件。

发布订阅模式:订阅者(subscriber)把自己想订阅的事情注册(subscribe)到调度中心(topic),当发布者(publisher)发布该事件(publish topic)到调度中心时,也就是该事件触发时,由调度中心统一调度(trigger event)订阅者注册到调度中心的处理程序。

 

2.4 经典案例代码实现

观察者模式:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <script>
        // 以猎人来举例。每个猎人都具有发布任务、订阅任务的功能。每人都有一个订阅列表来存储订阅了自己的人。
        // 假设b订阅了a,那么当a状态发生改变时,b会收到消息并自动调用处理程序。
        class Hunter {
            constructor(name, subscribers) {
                this.name = name
                this.subscribers = []
            }

            publish(money) {
                console.log(this.name + '发布...消息, 价值', money + '元')
                this.subscribers.forEach((item) => {
                    item.cb(money)
                })
            }

            setSubscribers(item) {
                this.subscribers.push(item)
            }

            subscribe(target, cb) {
                console.log(this.name + '订阅了' + target.name)
                target.setSubscribers({ cb })
            }
        }

        const a = new Hunter('a')
        const b = new Hunter('b')
        const c = new Hunter('c')

        b.subscribe(a, (money) => {
            if (money > 1000) {
                console.log('钱够了,b收到消息')
            }
        })

        c.subscribe(a, (money) => {
            console.log('c收到消息')
        })

        a.publish(2000)
    </script>
</head>
<body>
    
</body>
</html>

运行结果:

 

 

发布订阅模式:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <script>
        // 多了一个调度中心,猎人想发布任务要去调度中心发布,发布后订阅了这个任务的人会自动收到通知。
        // 比如猎人a去调度中心发布一个任务tiger,这时订阅了tiger任务的b、c就会收到通知。
        const dispatch = {
            // 存储订阅的任务
            topic: {},
            // 猎人订阅任务
            subscribe: function(topic, cb) {
                if (!this.topic[topic]) {
                    this.topic[topic] = []
                }

                this.topic[topic].push({ cb })
            },
            // 猎人发布任务
            publish: function(topic, money) {
                if (!this.topic[topic]) {
                    return
                }

                this.topic[topic].forEach(item => {
                    item.cb(money)
                })
            }
        }

        class Hunter {
            constructor(name) {
                this.name = name
            }

            publish(topic, money) {
                console.log(this.name + '发布了' + topic + '的任务')
                dispatch.publish(topic, money)
            }

            subscribe(topic, cb) {
                console.log(this.name + '订阅了' + topic + '的任务')
                dispatch.subscribe(topic, cb)
            }
        }

        const a = new Hunter('a')
        const b = new Hunter('b')
        const c = new Hunter('c')

        // a 发布任务,b、c订阅任务
        b.subscribe('tiger', (money) => {
            if (money > 2000) {
                console.log('b: 任务钱够多,可以搞')
            }
        })

        c.subscribe('tiger', (money) => {
            console.log('c:可以搞')
        })

        a.publish('tiger', 3000)
    </script>
</head>
<body>
    
</body>
</html>

观察者模式和发布订阅模式最大的区别就是发布订阅模式有个事件调度中心。

观察者模式由具体目标调度,每个被订阅的目标里面都需要有对观察者的处理,这种处理方式比较直接粗暴,但是会造成代码的冗余。

而发布订阅模式中统一由调度中心进行处理,订阅者和发布者互不干扰,消除了发布者和订阅者之间的依赖。这样一方面实现了解耦,还有就是可以实现更细粒度的一些控制。比如发布者发布了很多消息,但是不想所有的订阅者都接收到,就可以在调度中心做一些处理,类似于权限控制之类的。还可以做一些节流操作。

 

2.5 发布订阅模式是不是观察者模式

网上关于这个问题的回答,出现了两极分化,有认为发布订阅模式就是观察者模式的,也有认为观察者模式和发布订阅模式是真不一样的。

其实我不知道发布订阅模式是不是观察者模式,就像我不知道辨别模式的关键是设计意图还是设计结构(理念),虽然《JavaScript设计模式与开发实践》一书中说了分辨模式的关键是意图而不是结构

如果以结构来分辨模式,发布订阅模式相比观察者模式多了一个中间件订阅器,所以发布订阅模式是不同于观察者模式的;如果以意图来分辨模式,他们都是实现了对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都将得到通知,并自动更新,那么他们就是同一种模式,发布订阅模式是在观察者模式的基础上做的优化升级。

不过,不管他们是不是同一个设计模式,他们的实现方式确实有差别,我们在使用的时候应该根据场景来判断选择哪个。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

jsonbro

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值