文章来源:itsCoder 的 WeeklyBolg 项目
itsCoder主页:http://itscoder.com/
作者:阿风
为什么
为什么要写这篇文章?
网上有相关的文章吗?当然有
那你为什么还要写?我就是要写
女票最近在学习设计模式,学到了观察者模式和发布/订阅模式,她看完了书以后对我说这有什么区别?于是我跟她说,这当然有区别,抄起笔就给她讲有什么区别。
那讲懂了吗?当然没有
为什么呢?她在乎的是你的态度(参考女票车发动不了的梗)
所以我要写这篇文章。别问为什么,问就右上角。
直观对比
废话不多说,先上图,看图是帮助理解最直观的方式。
这个图,应该说很直观的告诉了你,这两种设计模式有什么区别。
那有什么区别呢?右…右边多了个方框?对啊,就是多了个方框。
关键的问题是,为什么要比观察者模式多一个方框?
观察者模式
既然发布/订阅是比观察者模式多了一些东西,那么我们就先来看观察者模式。
你可以去Google,可以在很多不同的文章看到这么一句话:定义对象间一种一对多的依赖关系。
观察者模式是软件设计模式的一种。在此种模式中,一个目标对象管理所有相依于它的观察者对象,并且在它本身的状态改变时主动发出通知。这通常透过呼叫各观察者所提供的方法来实现。此种模式通常被用来实时事件处理系统。 —— 维基百科
简单来说就是,A 对象依赖 B 对象的某个状态变化来执行一些逻辑,那么 A 就需要观察 B 的某个状态,B 会在这个状态发生改变时通知 A,并且可以把描述这个状态的数据给A。
能看明白不?我举个栗子
大家都上过学吧,假设你是个学渣,你同桌是个学霸,今天要考试了,卷子发下来,你几乎一道题都不会做,于是你对同桌说:你做完了给我抄一下,你同桌做完了,拿胳膊肘捅你一下,把试卷往你这边挪。
你是观察者,你同桌是被观察者,你观察的状态是你同桌做完了,你同桌的试卷是你要的数据,这个状态发生的时候你要处理的逻辑是抄答案。
抄过作业的都知道,你不可能一直盯着你同桌或者一直问他写完没吧?在程序中,如果对象 A 一直不停的去获取 B 的某个状态来看有没有发生改变,这不是观察者模式,这是轮询。
那什么是一对多的依赖关系?
你同桌是学霸,你同桌前后左右都跟你一样是学渣,你们都得抄他的,不然全不及格。明白了吗?
维基百科那段话,最后一句— 此种模式通常被用来实时事件处理系统。
我们回到程序中来,做为前端/客户端工程师最为常见的一种场景:一个按钮被点击了,程序打开一个页面。所谓的监听这个按钮的点击事件不就是一种观察者模式吗?
发布/订阅模式
我们还是先看维基百科
在软件架构中,发布-订阅是一种消息范式,消息的发送者(称为发布者)不会将消息直接发送给特定的接收者(称为订阅者)。而是将发布的消息分为不同的类别,无需了解哪些订阅者(如果有的话)可能存在。同样的,订阅者可以表达对一个或多个类别的兴趣,只接收感兴趣的消息,无需了解哪些发布者(如果有的话)存在。
能看明白不?网上有很多类似下面的例子,在我看来并不是发布/订阅模式
你在淘宝上看上了一款键盘,但是没货了,于是你给店家说到货了告诉我,店家说好的,到货了告诉你。
我再举个栗子
你看上了一款键盘,你找了个代购跟他说找到这个键盘就买给我,这个代购路子野有很多渠道,键盘到货后这些渠道商会告诉这个代购键盘到货了,代购买下来发给你
前者和后者有什么区别?这两个例子中消息都是键盘,而第一个消息的发布者是店家,你是消息的接收方,你明确的知道这个键盘是这个店家的,店家也明确知道你买了这个键盘,键盘出了问题你能找这个店家售后。
而后者消息的发布者是那个代购的某一个渠道商,代购是中间件,你是消息的接收方,你并不清楚这个键盘到底是哪家店的,那家店也并不清楚这个键盘最后是被谁买走了,键盘出了问题你都找不到店家售后。(不要跟我抬杠说现在代购也知道是在哪家店买的,我这个栗子是要说清楚发布/订阅的关键点)
所以前者应该是观察者模式,后者才是发布订阅模式。
回到程序中来,比方说一个应用区分登录状态和未登录状态的UI,很多不同的页面都可以实现登录的逻辑,在登录成功后,所有当前被打开的页面都需要更新UI。这种情况就可以使用发布/订阅模式,在某个页面登录成功后发布登录成功的事件,有中间件将这个事件分发给其他页面更新UI,注意其他页面关心的只是登录状态改变这件事,而不关心用户具体在哪个页面登录的
区别
这两种模式区别可以简单归结为是观察状态还是事件。
观察者模式中
- 状态发布者维护了一个观察者的列表,明确的知道有哪些观察者存在,将状态变化直接通知给观察者
- 状态的观察者也明确的知道自己观察的状态是描述的哪一个对象
- 甚至需要这种相互知道的关系来处理逻辑(比如需要明确知道哪一个按钮被点击,处理对应的逻辑)
发布/订阅模式中
- 事件的发布者只发布事件,不关心这个事件被谁获取了,通常将事件发给一个中间件,由中间件再去分发事件
- 事件的订阅者只关心事件本身,不关心这个事件是谁发布的,通常在中间件中去注册观察某个事件
- 中间件中去维护事件类别对应的订阅者列表,当收到事件后,去对应列表中通知订阅者们
场景
最重要的是知道什么场景应该用什么设计模式。可以按以下原则来判断
- 是否观察的是状态(明确知道状态源)
如果被观察和观察双方需要明确知道对方,那就观察者模式,否则发布订阅模式
- 一对一或者一对多的关系
这个事件或者状态只有一个发布者,两种都可以用,再参考第一条
- 多对多的关系
首先所谓对多对的关系基本就可以确定传递的是事件,而不是状态,因为不同对象不应该发布相同的状态,不要犹豫选发布/订阅,如果你一定要用观察者模式来实现事件的传递,那么看下面这个图,耦合明白吗?另外考虑内存泄露反注册,用观察者模式你确定你都反注册完了?
写在最后
设计模式的学习不要局限在书本概念上,在写代码的过程中,学习优秀源码的过程中都可能在默默的接触不同的设计模式。设计模式本身就是在解决程序中的场景合理化问题,在实践中去理解不同的设计模式,甚至去思考现有模式的变种去解决实际的场景问题。