目录
Observer(观察者,Behavioral Pattern)
问题:看孩子
- 孩子哭了,爸爸要立刻检查孩子并安慰他
- 我们可以给爸爸单独开一个线程看着孩子
- 但是这个是一个一直进行的循环,很占用CPU
- 试了一下,真挺猛的,中间突然高的那一段就是程序运行的时候
- 如果我还想要妈妈也看着,也很简单,只要把爸爸代码复制一遍改个名字
- 然后再运行
- 如果有三个孩子,根据我们的设计,一个爹/妈只能看一个娃,所以会有三个人来看这三个娃,CPU会受不了的
- 我们要想办法!
- 平常我们看孩子不可能一直在看,都是要听到孩子哭才去看,所以这里的想法就是不要爸爸去一直观察,而是等着娃哭,再去观察
- 而且我们的Observer还不够nb,如果不用一直观察的话,那我一个爹就可以管一堆娃了,同理,一个娃也可以有一堆观察者
- 这就引出了我们的观察者模式
Observer Pattern
它是啥
- 根据我们上面的例子,针对这种情境,我们首先要有被观察的对象,我们称之为Subject,Subjects会改变状态,孩子可以哭或者高兴,商品价格可以升或降,你喜欢的姑娘可以有男朋友或者单身
- 我们还要有观察者Observer,比如看孩子的爸爸妈妈,看商品价格的剁手党,看姑娘是否单身的你
- 从看孩子过程中的问题我们可以发现,Observer应当是可以观察多个对象,同样一个Subject也被多个Observer观察
- 爹妈一起看一个孩子,爹一个人也能看一堆孩子
- 商品可以被一堆人等着降价,你也可以等着一堆漂亮衣服降价
- 一堆人等着漂亮姑娘单身,你也可以等着一堆漂亮姑娘单身
- 还有一个小问题,Observer不能一直看着Subject啊,生活中是浪费精力,计算机中是浪费内存
- 我们完全可以让Subject在改变状态时自己通知Observers
- 以上就是观察者模式的基本构成和大致原理
- by the way,观察者模式还被称为Dependents,Publish-Subscribe,Model-View
- 总结来说,观察者模式就是用来维持相关的对象之间的一致性,而且是一种松耦合的方式,以下情况适用观察者模式
- 一个抽象有两个相互依赖的方面时,可以把这两个方面单独封装,以方便重用和改变
- 改变一个对象需要改另一个对象,而且还不知道要改几个对象时
- 当一个对象需要能通知其他对象还不必知晓都是谁时,也就是不想要太紧的耦合时
咋实现?
- 我直接上图啊哈哈哈哈
- 这两个图意思都差不多,首先我们要有两个接口,之前说过几个设计原则,我们这波直接用上,面向接口编程,减小耦合,依赖倒置
- 具体流程是这样的
- Subject要能将Observer加入自己的观察者集合里面(也就是说Subject要有一个集合存储Observer以实现被多个观察者观察)
- 有添加自然有删除,也就是Detach功能
- 然后就是Subject的通知功能了,当Subject状态改变时,要调用Subject的Notify方法,遍历观察者列表中的观察者,调用它们的Update功能
- 说到Observer的Update方法,就是字面意思,更新当前对象的状态,在看小孩的例子中,我们可以把小孩这个对象作为参数传入Update方法,由观察者来把小孩的状态改成开心的状态
- 以上是抽象的接口,然后只要有具体的Observer和Subject的实现,观察者模式就完成了
- 大概就这个感觉,如下图,看图别太较真,懂的都懂
优缺点!
- 没有银子弹,一种模式必有其优缺点
- 好处
- 减小了观察者和主题(被观察对象)之间的耦合
- 可以用来支持事件广播,可以把状态改变的信息通知所有订阅的观察者
- 不太妙的地方
- 可能会有不期望的更新,比如你告诉喜欢的姑娘没有男朋友就告诉你,结果人家干啥都跟你说,,可能例子体会不出,但是实现观察者模式时可能会不小心就获取了一些不期待的状态更新
- 很有可能会有错误的更新,比如状态改变和通知的顺序反了,姑娘还没分手然后她告诉你分手之后才分,可能会有一些不太好的事情发生
实现细节
- subject可以通过集合来存储观察者
- 当observer同时观察多个Subject时候,可以让Subject通过Update的接口告诉Observer它是谁,比如把对象传进Update
- 触发Update的可以是Subject,也可以是观察者在触发了许多状态改变时,或者是某些第三方的对象
- 一定要确保状态的改变在通知之前!!!
- Subject在通知Observer自己有什么变化时有两种方式
- Push模型,把一堆改变的数据传过去
- Pull模型,传引用,让观察者自己找变化,
- 观察者也可以称为Subject
- 当观察者只想在几个状态都改变之后被通知,我们可以用一个中介对象,作为Mediator
比较出名的应用
- Smalltalk的Model/View/Controller 用户界面框架
- Model=Subject
- View=Observer
- Controller是改变Subject状态的东西
- Java AWT/Swing的事件模型
java内置的观察者模式
- java中实际上是有内置的观察者模式的
- 只是由于Subject需要有一个集合来存储Observer,而接口中不能有集合,java内置的实现就把Subject直接做成一个类Observable
- 这个内置的观察者模式已经过时了,现在只为学习需要
- 我们可以看到,Observable类中有集合,相当于Subject
- 这里的Observer的Update方法很有意思,有两个参数,一个是Subject o,一个是Object arg,可以把要传的数据封装进一个对象作为arg传进去,也就是说,这个Update可以我们之前所说的Pull和Push都实现
- 以下为Observable的结构
- 可以看到add和delete观察者的方法,要注意的是notify的方法只有在setChanged被调用之后才能调用Update方法
- 没错,神圣的setChanged方法其实就是把changed改个值
- 这也就防止了之前讨论的还未修改就通知的情况发生
- 有两个通知观察者的方法,第二个方法就是把一个封装好的数据对象Push给Observer,然后再传进update,实现了Push的模型
- notify Observers()
- notify Observers(Object arg)
- 如果调用第一个就是pull的模型
- 问题
- Observable是一个类
- Observable中重要的方法setChanged是被保护的,只有子类能调用,所以说,如果想调用这个setChanged只能继承它,这就违反了我们所说的用组合/聚合代替继承的原则
- 解决方案
- 我们可以用一个特殊的Subject包含一个Observable对象
- 然后把这个Subject需要的Observable行为委派给这个对象
java实现标准的观察者模式
- java内置的观察者模式不标准在它把Observable改成了类,我们依旧可以写Subject接口,在Subject的实现中放集合即可
实例:Java AWT/Swing中的事件模型
- GUI组件可以产生GUI事件,它们是事件源,事件源就是观察者模式中的Subject
- ActionListener,事件监听器就是观察者模式中的Observer
- Subject要向Observer列表中注册Observer
- AbstractButton相当于Subject接口
- AbstractButton中fireActionPerformed方法相当于notify
- ActionListener相当于Observer接口
- Listener中的actionPerformed方法相当于update
实例:天气管理应用
- 一种实现
观察者模式实现的原则
- 把程序中变的方面与不变的分开
- 观察者模式中变化的内容是主题的状态以及观察者的数量和类型。使用此模式,可以改变依赖于主题状态的对象,而无需更改该主题
- 依赖倒置原则DIP
- 面向接口编程
- 组合/聚合复用原则CRP