23种设计模式之观察者模式
观察者模式的定义
定义: 定义对象间一种一对多的依赖关系,使得每当一个对象改变状态,则所有依赖于它的对象都会得到通知并被自动更新
通俗的说, 就是一个类的某个条件满足时, 会调用一系列定义好的方法
其类图如下:
其中的4个角色:
- Subject 被观察者: 定义被观察者必须实现的职责, 它必须能够动态的增加、取消被观察者. 它一般是抽象类或者实现类, 仅仅完成作为被观察者必须实现的职责: 管理观察者并通知观察者
- Observer 观察者: 观察者接收到消息后, 即进行 update 操作, 对接收到的信息进行处理
- ConcreteSubject 具体的被观察者: 定义被观察者自己的业务逻辑, 同时定义对哪些事件进行通知
- ConcreteObserver 具体的观察者: 每个观察者在接收到消息后的处理反应是不同的, 每个观察者有自己的业务逻辑
被观察者抽象类代码:
具体被观察者代码:
观察者接口代码:
具体的观察者代码:
场景类如下:
观察者模式的应用
观察者模式的优点:
- 观察者和别观察者之间是抽象耦合. 如此设计, 不管是增加观察者还是被观察者都非常容易扩展
- 建立一套触发机制.
观察者模式的缺点:
观察者模式需要考虑并发效率和运行效率问题, 一个被观察者, 多个观察者, 开发和调试就会比较复杂, 而且在Java中消息的通知是顺序执行, 一个观察者卡壳, 会影响整体的执行效率. 在这种情况下, 一般考虑采用异步的方式
观察者模式的使用场景:
- 关联行为场景. 关联行为是可拆分的, 而不是"组合"关系
- 时间多级触发场景
- 跨系统的消息交换场景
观察者模式的注意事项
1.广播链的问题
一个观察者可以有双重身份, 既是观察者, 也是被观察者, 链一旦建立, 这个逻辑就比较复杂, 可维护性非常差. 根据经验建议, 在一个观察者模式中最多出现一个对象既是观察者也是被观察者, 也就是说消息最多转发一次, 这还是比较好控制的.
观察者模式和责任链模式的最大区别就是观察者广播链在传播的过程中消息是随时更改的, 它是由相邻的两个节点协商的消息结构; 而责任链模式在消息传递过程中基本上保持消息不可变, 如果要改变,也只是在 原有的消息上进行修正
2.异步处理问题
被观察者发生动作了, 观察者要做出回应, 如果观察者比较多, 而且处理时间比较长怎么办, 用异步呗, 异步处理就要考虑线程安全和队列的问题
观察者模式的扩展
1.Java 世界中的观察者模式
在Java中, java.util.Observable 实现了被观察者的功能, 被观察者直接继承即可, java.util.Observer 是观察者接口, 已经写好了
2.项目中真实的观察者模式
- 观察者和被观察者之间的消息沟通. 被观察者状态改变会触发观察者的一个行为, 同时会传递一个消息给观察者, 在实际中一般的做法是: 观察者中的update方法接受两个参数, 一个是被观察者, 一个是DTO(Data Transfer Object, 数据传输对象), DTO一般是一个纯洁的JavaBean, 由被观察者生成, 当然, 如果考虑到远程传输, 一般消息是XML格式传递
- 观察者响应方式. 如果观察者来不及相应, 被观察者的时间就会被拉长. 观察者如何快速响应呢? 有两个办法: 一是采用多线程技术, 也就是通常说的异步架构; 二是缓存技术.
- 被观察者尽量自己做主. 在设计的时候要考虑灵活性,否则会加重观察者的业务逻辑, 一般是这样做的, 对被观察者的业务逻辑 doSomething 方法实现重载, 如增加一个 doSomething(boolean isNotifyObs) 方法, 决定是否通知观察者, 而不是在消息到达观察者时才判断是否要消费
观察者模式在实际项目和生活中的例子:
- 文件系统. 比如, 在一个目录下新建立一个文件, 这个动作会同时通知目录管理器增加该目录, 并通知磁盘管理器减少1Kb的空间, 这里 "文件" 是一个被观察者, "目录管理器"和"磁盘管理器"则是观察者
- 猫鼠游戏. 夜里猫叫一声,家里老鼠撒腿就跑, 同时也吵醒了熟睡的主人, 这里"猫"就是被观察者, 老鼠和人则是观察者
- ATM取钱. 在ATM机器上取钱,多次输错密码, 卡被ATM吞掉, 吞卡动作发生时, 会触发哪些事件呢?第一, 摄像头连续快拍, 第二, 通知监控系统,吞卡发生, 第三, 初始化ATM机屏幕, 返回最初状态. 一般前两个动作都是通过观察者模式完成的, 最后一个动作是异常来完成的
- 广播收音机. 电台在广播, 可以打开一个收音机听, 也可以打开两个收音机来收听, 电台就是被观察者, 收音机就是观察者