设计模式私人笔记-观察者模式

前言:设计模式,一个老掉牙的话题java实现设计模式,一个永恒的话题 。所有的Java开发,都必须经过的话题,都必须学习的话题 。但是其实很多时候自己只是看了,了解了,然后忘了 。所以还是想从头梳理一遍重要的设计模式,给自己看 。不求逻辑多清晰,内容多完善,UML图画的多严谨,理解、会意就好 。
遇到什么问题
提出一个经典的需求:假设现在有一个报价系统,当某个产品的价格发生变化时,就会通过短信发送给相关客户 , 也会通过邮件给该客户发送价格变动情况 。
用最经典的方法来实现该需求 。这里只是做了一个非常简化的实现 。
创建两个服务,一个用于发送短信,一个用于发送邮件:
创建一个服务,用于处理价格变动的逻辑:
基于该设计的使用代码:
该设计方式非常简单 , 也是非常非常常见的实现方案 。但是该方案很明显存在一些问题:
· 两个感知价格变动的服务和,没有抽象出接口,虽然感知价格的方法一致;
· 如果要新添加感知价格变动的服务 , 比如我现在要求把每次变动的价格计入日志系统 , 又需要增加一个实体类 ;并且,每次增加新的价格变动感知服务,都要添加为的成员,然后由手动去依次调用;
简单来说 , 就是,这种实现方案,扩展性太差!
应用观察者模式
先简单说明一下什么是观察者模式 。举个经典的案例:公众号的 。当一个用户了某个公众号,就相当于该公众号把用户添加到了用户列表,这样一来,只要该公众号有消息更新,即可按照用户列表中的用户依次推送消息 。如果用户不愿意收到该公众号的信息,就取消即可 。
在Head First Java 一书中,把观察者模式定义为:
观察者模式定义了对象之间的一对多依赖,这样一来,当一个对象改变状态时,他所有依赖者都会收到通知并执行更新 。
· 有状态对象的状态更新;
· 通知所有注册的观察者进行更新; 其实就这两步 。下面先用观察者模式重写上面的案例 。
首先为主题对象(有状态对象)定义一个接口:
【设计模式私人笔记-观察者模式】· 注意,第一个方法用于注册价格感知对象,即之前的,,或者其他的需要监控价格变化的对象;
第二步,为所有需要感知价格变化的业务统一抽取一个接口:
该接口中就一个方法 , 执行价格的改变感应;那么方法传入的price就是变化之后的price;
然后重新实现发送邮件和短信的服务:
在之前的版本之上 , 只是实现了接口;
最后,重新修改我们的价格改变对象:
在之前的版本基础上,实现了接口,因为一对多,先简单的使用List来存放 。在方法中,分别调用注册的每一个价格感知器的业务方法 。
这个版本的测试代码:
好处
· 完全面向接口编程;
· 扩展方便,如果我现在要增加记录日志的功能,只需要:
不需要修改本身的代码,只需要在执行之前java实现设计模式,把注册到中:
即可 。
标准的观察者模式
上面的例子,仅仅只是应用观察者模式的案例,现在来抽象出标准的观察者模式 , 看类图:
有几个点注意:
· :抽象主题(接口) , 就是包含状态发生改变的那个类的抽象;在该接口中 , 定义了添加观察者,删除观察者和通知观察者三个方法;
· :主题接口的实现;
· :抽象的观察者(接口),就是上面说到的感知改变的对象的抽象 , 定义了一个感知改变之后的业务逻辑方法 。这个方法中定义了两个参数,(本次产生改变的主题对象),(本次变化的数据),关于这两个参数 , 涉及到改变数据的推送和拉取两种模式,后面介绍,这里就简单理解为用这两个参数来获取改变的数据就可以了(第二个看成上面的即可) 。
下面写一段标准观察者模式示例代码 。
定义接口:
定义观察者接口:

设计模式私人笔记-观察者模式

文章插图
设计模式私人笔记-观察者模式

文章插图
定义具体的主题类实现,仍然先使用简单的List来存储注册的观察者;
最后做一个观察者的实现:
使用起来也很简单,只需要和上面示例中一样 , 创建好具体的主题对象和具体的观察者,将观察者注册到主题对象中即可 。
Java中的观察者模式
因为观察者模式使用非常频繁,甚至被誉为设计模式的皇后(不做评价),所以Java其实已经提供了观察者模式的实现 。
在java.util包中 , 有两个类型:
· java.util.:可被观察对象,即我们的;
· java.util.:观察对象,就是我们上面的;
我们来看看这两个类的结构,有一个直观了解:
首先 , 接口的实现和我们上面标准的观察者接口定义相同,仍然支持推送和拉取两种获取变化数据的方式,不过多说明;
下面重点看看定义:
注意到几个点: 1,首先,是一个抽象类 。我们知道,不建议将这种抽象定义为抽象类,会让我们的类组织关系变得非常死板,这是设计上的一个问题,我们先跳过(这个类是JDK1.0就存在了,兼容问题没有更新,后面会介绍更好的方法) 。2,我们看到了和方法 , 很明显,这两个方法用于维护观察者; 3,方法,顾名思义 , 用于移除掉所有该主题上的观察者; 4,方法和()两个方法,很明显都是告知观察者的方法,只是一个可以主动传递改变的值(推送),一个不需要传递改变的值(这个值可以主题对象通过方法提供,即拉?。?。4, , 和,这三个方法比较奇怪,肯定是属于一组内容的,具体是什么 , 我们后面介绍 。这里只需要注意一个重点,就是在我们调用方法之前,我们必须调用方法,告知我们的改变已经成立(为什么这么设计,后面讨论)
如果还有点模糊,我们直接使用java提供的观察者模式支持类,完成我们的价格监控的实例:
从最简单的发送邮件服务开始:
我们只需要实现接口,然后在方法中完成逻辑即可 。这里需要注意,我们采用的是推送的方式,即我们直接从第二个参数中获取了price的数值,那么意味着主题对象中,需要调用()方法来推送数据(注意观察后面的代码);
记录日志服务:
接下来,主题对象的实现:
注意代码中的注释,我们在方法调用之前,一定要调用方法 。
基于Java的观察者模式的测试代码:
测试代码很简单 。
小结,那么Java提供的观察者模式到底为我们做了些什么?很明显 , 主题对观察者的维护工作,主题对观察者的通知动作,都放到了类中 。那我们来简单分析一下类的源码 , 主要解决两个问题:
· 怎么维护观察者;
· 方法到底用来干嘛;
类源码分析
· 维护两个成员变量,和obs,其中代表数据是否确定改变,obs是一个的 , 虽然性能差,但确是线程安全的,这点是选用的原因 。
· 构造方法中初始化;
· 这里列出了和观察者相关的所有方法,首先注意,方法都是的,保证了线程安全;
· 这是关于通知观察者,调用观察者更新方法的代码 。在代码注释中已经说明两个方法的区别;
· 在方法中,我们注意到声明了一个局部变量[] ;并使用 = obs.();将当前中的观察者拷贝到了中,最后在执行通知的时候 , 遍历的是中的观察者引用;这样做的目的在于,使用作为一个当前执行通知时的观察者快照,那么,在真正只是方法的时候 , 不会造成正常的添加和删除;对并发下的性能有一定帮助;但是可能会存在两个问题需要注意:
· 可能会有刚加入的观察者没法及时获取正在变更的数据;
· 可能会有刚删除的观察者仍然受到了最近正在变更的数据;
· 代码中,使用if(!) 来判定数据是否真正改变 。接下来看看相关的方法:
使用了三个方法来操作变量,代码很简单 。但是我们通过这两段代码,也能清楚的看出 , 为什么我们在调用方法之前,一定要调用方法了 。
可能有童鞋会觉得这样的设计有点多此一举,但是考虑到,如果一个数据变化过于频繁,那么我们在某些情况下,也可以通过变量来控制真正的发送数据变更消息的时机,这也是一个设计初衷吧 。
Java的体系有什么问题?
上面已经看了Java本身提供的观察者模式的具体使用和相关代码实现,那么,这样设计有问题么?
回答是肯定的 。有问题 。所以其实我们发现 , 真正使用Java的体系的代码也不多 。主要原因我认为有两个: 1,是一个类 , 这个本身就是很烦的事情,我的主题类需要继承它,代码结构会有问题; 2 , 中重要的方法,比如方法,导致除了继承 , 没法在外部通过第三方代码来促使主题确定改变,在一些场景中,有较大局限性 。
一些相关话题
上面已经对观察者模式做了较完整的梳理,完了么?还没有 。从观察者,我们至少能够延伸出下面两个问题:
· 观察者模式和事件监听模式有什么关系?
· 观察者模式和发布订阅模式有什么关系?
这里又引出了两个模式:事件监听模式和发布订阅模式,这两个模式又可以引申出一大堆问题 。我们在分文章来独立阐述 。
本文到此结束,希望对大家有所帮助 。