|
| 1 | +> 文章来源:itsCoder 的 [WeeklyBolg](https://github.com/itsCoder/weeklyblog) 项目 |
| 2 | +> |
| 3 | +> itsCoder主页:[http://itscoder.com/](http://itscoder.com/) |
| 4 | +> |
| 5 | +> 作者:[小旋风](http://extremej.itscoder.com/about/) |
| 6 | +> |
| 7 | +> 审阅者:[小山](https://ljuns.itscoder.com/),[hymanme](http://hymane.itscoder.com/) |
| 8 | +
|
| 9 | +### 为什么 |
| 10 | + |
| 11 | +为什么要写这篇文章? |
| 12 | + |
| 13 | +网上有相关的文章吗?当然有 |
| 14 | + |
| 15 | +那你为什么还要写?我就是要写 |
| 16 | + |
| 17 | +女票最近在学习设计模式,学到了观察者模式和发布/订阅模式,她看完了书以后对我说这有什么区别?于是我跟她说,这当然有区别,抄起笔就给她讲有什么区别。 |
| 18 | + |
| 19 | +那讲懂了吗?当然没有 |
| 20 | + |
| 21 | +为什么呢?她在乎的是你的态度(参考女票车发动不了的梗) |
| 22 | + |
| 23 | +所以我要写这篇文章。别问为什么,问就右上角。 |
| 24 | + |
| 25 | +### 直观对比 |
| 26 | + |
| 27 | +废话不多说,先上图,看图是帮助理解最直观的方式。 |
| 28 | + |
| 29 | + |
| 30 | + |
| 31 | +这个图,应该说很直观的告诉了你,这两种设计模式有什么区别。 |
| 32 | + |
| 33 | +那有什么区别呢?右...右边多了个方框?对啊,就是多了个方框。 |
| 34 | + |
| 35 | +关键的问题是,为什么要比观察者模式多一个方框? |
| 36 | + |
| 37 | +### 观察者模式 |
| 38 | + |
| 39 | +既然发布/订阅是比观察者模式多了一些东西,那么我们就先来看观察者模式。 |
| 40 | + |
| 41 | +你可以去Google,可以在很多不同的文章看到这么一句话:**定义对象间一种一对多的依赖关系**。 |
| 42 | + |
| 43 | +> **观察者模式**是[软件设计模式](https://zh.wikipedia.org/wiki/%E8%BB%9F%E4%BB%B6%E8%A8%AD%E8%A8%88%E6%A8%A1%E5%BC%8F)的一种。在此种模式中,一个目标对象管理所有相依于它的观察者对象,并且在它本身的状态改变时主动发出通知。这通常透过呼叫各观察者所提供的方法来实现。此种模式通常被用来实时事件处理系统。 —— 维基百科 |
| 44 | +
|
| 45 | +简单来说就是,A 对象依赖 B 对象的某个状态变化来执行一些逻辑,那么 A 就需要观察 B 的某个状态,B 会在这个状态发生改变时通知 A,并且可以把描述这个状态的数据给A。 |
| 46 | + |
| 47 | +能看明白不?我举个栗子 |
| 48 | + |
| 49 | +> 大家都上过学吧,假设你是个学渣,你同桌是个学霸,今天要考试了,卷子发下来,你几乎一道题都不会做,于是你对同桌说:**你做完了给我抄一下**,你同桌做完了,拿胳膊肘捅你一下,把试卷往你这边挪。 |
| 50 | +
|
| 51 | +你是观察者,你同桌是被观察者,你观察的状态是你同桌做完了,你同桌的试卷是你要的数据,这个状态发生的时候你要处理的逻辑是抄答案。 |
| 52 | + |
| 53 | +抄过作业的都知道,你不可能一直盯着你同桌或者一直问他写完没吧?**在程序中,如果对象 A 一直不停的去获取 B 的某个状态来看有没有发生改变,这不是观察者模式,这是轮询。** |
| 54 | + |
| 55 | +那什么是一对多的依赖关系? |
| 56 | + |
| 57 | +>你同桌是学霸,你同桌前后左右都跟你一样是学渣,你们都得抄他的,不然全不及格。明白了吗? |
| 58 | +
|
| 59 | +维基百科那段话,最后一句— 此种模式通常被用来实时事件处理系统。 |
| 60 | + |
| 61 | +我们回到程序中来,做为前端/客户端工程师最为常见的一种场景:一个按钮被点击了,程序打开一个页面。所谓的监听这个按钮的点击事件不就是一种观察者模式吗? |
| 62 | + |
| 63 | +### 发布/订阅模式 |
| 64 | + |
| 65 | +我们还是先看维基百科 |
| 66 | + |
| 67 | +> 在[软件架构](https://zh.wikipedia.org/wiki/%E8%BD%AF%E4%BB%B6%E6%9E%B6%E6%9E%84)中,**发布-订阅**是一种[消息](https://zh.wikipedia.org/wiki/%E6%B6%88%E6%81%AF)[范式](https://zh.wikipedia.org/wiki/%E8%8C%83%E5%BC%8F),消息的发送者(称为发布者)不会将消息直接发送给特定的接收者(称为订阅者)。而是将发布的消息分为不同的类别,无需了解哪些订阅者(如果有的话)可能存在。同样的,订阅者可以表达对一个或多个类别的兴趣,只接收感兴趣的消息,无需了解哪些发布者(如果有的话)存在。 |
| 68 | +> |
| 69 | +> 发布/订阅是[消息队列](https://zh.wikipedia.org/wiki/%E6%B6%88%E6%81%AF%E9%98%9F%E5%88%97)范式的兄弟,通常是更大的[面向消息中间件](https://zh.wikipedia.org/w/index.php?title=%E9%9D%A2%E5%90%91%E6%B6%88%E6%81%AF%E4%B8%AD%E9%97%B4%E4%BB%B6&action=edit&redlink=1)系统的一部分。 —— 维基百科 |
| 70 | +
|
| 71 | +能看明白不?网上有很多类似下面的例子,在我看来并不是发布/订阅模式 |
| 72 | + |
| 73 | +>你在淘宝上看上了一款键盘,但是没货了,于是你给店家说到货了告诉我,店家说好的,到货了告诉你。 |
| 74 | +
|
| 75 | +我再举个栗子 |
| 76 | + |
| 77 | +>你看上了一款键盘,你找了个代购跟他说找到这个键盘就买给我,这个代购路子野有很多渠道,键盘到货后这些渠道商会告诉这个代购键盘到货了,代购买下来发给你 |
| 78 | +
|
| 79 | +前者和后者有什么区别?这两个例子中消息都是键盘,而第一个消息的发布者是店家,你是消息的接收方,你明确的知道这个键盘是这个店家的,店家也明确知道你买了这个键盘,键盘出了问题你能找这个店家售后。 |
| 80 | + |
| 81 | +而后者消息的发布者是那个代购的某一个渠道商,代购是中间件,你是消息的接收方,你并不清楚这个键盘到底是哪家店的,那家店也并不清楚这个键盘最后是被谁买走了,键盘出了问题你都找不到店家售后。(不要跟我抬杠说现在代购也知道是在哪家店买的,我这个栗子是要说清楚发布/订阅的关键点) |
| 82 | + |
| 83 | +所以前者应该是观察者模式,后者才是发布订阅模式。 |
| 84 | + |
| 85 | +回到程序中来,比方说一个应用区分登录状态和未登录状态的UI,很多不同的页面都可以实现登录的逻辑,在登录成功后,所有当前被打开的页面都需要更新UI。这种情况就可以使用发布/订阅模式,在某个页面登录成功后发布登录成功的事件,有中间件将这个事件分发给其他页面更新UI,**注意其他页面关心的只是登录状态改变这件事,而不关心用户具体在哪个页面登录的** |
| 86 | + |
| 87 | +### 区别 |
| 88 | + |
| 89 | +这两种模式区别可以简单归结为是观察状态还是事件。 |
| 90 | + |
| 91 | +观察者模式中 |
| 92 | + |
| 93 | +- 状态发布者维护了一个观察者的列表,明确的知道有哪些观察者存在,将状态变化直接通知给观察者 |
| 94 | +- 状态的观察者也明确的知道自己观察的状态是描述的哪一个对象 |
| 95 | +- 甚至需要这种相互知道的关系来处理逻辑(比如需要明确知道哪一个按钮被点击,处理对应的逻辑) |
| 96 | + |
| 97 | +发布/订阅模式中 |
| 98 | + |
| 99 | +- 事件的发布者只发布事件,不关心这个事件被谁获取了,通常将事件发给一个中间件,由中间件再去分发事件 |
| 100 | +- 事件的订阅者只关心事件本身,不关心这个事件是谁发布的,通常在中间件中去注册观察某个事件 |
| 101 | +- 中间件中去维护事件类别对应的订阅者列表,当收到事件后,去对应列表中通知订阅者们 |
| 102 | + |
| 103 | +### 场景 |
| 104 | + |
| 105 | +最重要的是知道什么场景应该用什么设计模式。可以按以下原则来判断 |
| 106 | + |
| 107 | +- 是否观察的是状态(明确知道状态源) |
| 108 | + |
| 109 | +如果被观察和观察双方需要明确知道对方,那就观察者模式,否则发布订阅模式 |
| 110 | + |
| 111 | +- 一对一或者一对多的关系 |
| 112 | + |
| 113 | +这个事件或者状态只有一个发布者,两种都可以用,再参考第一条 |
| 114 | + |
| 115 | +- 多对多的关系 |
| 116 | + |
| 117 | +首先所谓对多对的关系基本就可以确定传递的是事件,而不是状态,因为不同对象不应该发布相同的状态,不要犹豫选发布/订阅,如果你一定要用观察者模式来实现事件的传递,那么看下面这个图,耦合明白吗?另外考虑内存泄露反注册,用观察者模式你确定你都反注册完了? |
| 118 | + |
| 119 | + |
| 120 | + |
| 121 | + |
| 122 | + |
| 123 | +### 写在最后 |
| 124 | + |
| 125 | +设计模式的学习不要局限在书本概念上,在写代码的过程中,学习优秀源码的过程中都可能在默默的接触不同的设计模式。设计模式本身就是在解决程序中的场景合理化问题,在实践中去理解不同的设计模式,甚至去思考现有模式的变种去解决实际的场景问题。 |
0 commit comments