1 定义:Observer Pattern,也叫做发布订阅模式(Publish / subscribe)
1.1 定义:Define a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically.(定义对象间一种一对多的依赖关系,使得每当一个对象改变状态,则所有依赖于它的对象都会得到通知并被自动更新。)
1.2 通用类图:
1.3 通用代码:
public abstract class Subject { // 定一个一个观察者数组 private Vector<Observer> obsVector = new Vector<Observer>(); // 增加一个观察者 public void addObserver(Observer o) { this.obsVector.add(o); } // 删除一个观察者 public void delObserver(Observer o) { this.obsVector.remove(o); } // 通知所有观察者 public void notifyObserver() { for (Observer o : this.obsVector) { o.update(); } } } public interface Observer { // 更新方法 public void update(); } public class ConcreteSubject extends Subject { // 具体的业务 public void doSomething() { /* * do something */ super.notifyObserver(); } } public class ConcreteObserver implements Observer { // 实现更新方法 public void update() { System.out.println("接收到信息,并进行处理!"); } } public class Test { public static void main(String[] args) { // 创建一个被观察者 ConcreteSubject subject = new ConcreteSubject(); // 定义一个观察则 Observer obs = new ConcreteObserver(); // 观察者观察被被观察则 subject.addObserver(obs); // 观察者开始活动了 subject.doSomething(); } }
2 优点
2.1 观察者与被观察者之间是抽象耦合:因此无论是增加观察者还是增加被观察者,都十分容易。
2.2 可以经过扩展,从而建立一条链状的触发机制。
3 缺点
3.1 在Java中消息的通知默认是顺序执行,一个观察者卡壳,会影响整体的执行效率。这时可以考虑采用异步的方式。(多线程呗)
3.2 多级触发时的效率更让人担忧,设计时需考虑。(就是上面所说的链状触发)
4 应用场景
4.1 使用关联行为的场景。注意是可拆分的关联,而非组合。
4.2 事件多级触发场景。
4.3 跨系统的消息交换场景,如消息队列的处理机制。(如kkPlayer的全局快捷键)
5 注意事项
5.1 广播链的问题:在一个观察者模式中最多出现一个对象既是观察者又是被观察者,消息转发最多一两次,还比较好控制。
5.2 异步处理问题:需要考虑线程安全和队列的问题。(可以参考Message Queue)
6 扩展
6.1 Java世界中观察者模式
在前面的通用类图及其源代码实现中,通过观察你也许会发现,抽象的被观察者subject仅帮我们关联了观察者和确定了通知机制;抽象的观察者仅确立了被通知的方法。
这些完全可以单独抽象出一个联络类,作为观察者或被观察者的职责,这样就很非常符合单一职责原则。
幸运的是,Java从天始诞生就提供了一个可扩展的父类,即Java.util.Observable,这个类专用于让别人去触发,Java.util.Observer接口则专注于对观察者通知。
此外,还应关注Java.util.Observer接口丰富的方法,可以动态的添加/删除观察者。
6.2 项目中真实的观察者模式:
因为前面讲解的都是太标准的模式,在系统设计中会对观察者模式进行改造或改装,主要是下面三方面。
A。观察者与被观察者之间的消息沟通:被观察者状态改变时会触发观察者的一个行为,同时会传递一个消息给观察者,在实际中一般的做法是:观察者中的update方法接受两个参数,一个是被观察者,一个是DTO(Data Transfer Object,数据传输对象),DTO一般是一个纯JavaBean,由被观察者生成,由观察者消费。(若远程,则以XML格式传递)
B。观察者响应方式:观察者是个比较复杂的逻辑,要接受被观察者传递过来的信息,同时还要对他们进行逻辑处理,如果一个观察者对应多个被观察者,则需要考虑性能。备选方法两个:一是多线程,一是缓存技术。
C。被观察者尽量自己做主:不要把消息传到观察者时才判断是否需要消费。
应用举例:
文件系统:当在目录下新建一个文件,这个动作会同时通知目录管理器增加该目录,并通知磁盘管理器减少1KB的空间,也就是“文件”是一个被观察者,“目录管理器”则是观察者。
猫鼠游戏:猫叫一声,惊动了鼠;
广播收音机:电台在广播,收音机就能收听。
7 范例
7.1 标准的“观察者模式”可以参考通用源码
7.2 这里使用原书例子,使用java提供的观察者服务类。
类图如下:
需要注意的是,源码中有模拟处理耗时的情况,当然默认并没有多线程支持。。。
源代码如下:(作者原书例)
import java.util.Observable; /** * @author cbf4Life [email protected] I'm glad to share my knowledge with you * all. 韩非子,李斯的师弟,韩国的重要人物 */ public class HanFeiZi extends Observable { // 韩非子要吃饭了 public void haveBreakfast() { System.out.println("韩非子:开始吃饭了..."); // 通知所有的观察者 super.setChanged(); super.notifyObservers("韩非子在吃饭"); } // 韩非子开始娱乐了,古代人没啥娱乐,你能想到的就那么多 public void haveFun() { System.out.println("韩非子:开始娱乐了..."); super.setChanged(); this.notifyObservers("韩非子在娱乐"); } } public class LiSi implements Observer { // 首先李斯是个观察者,一旦韩非子有活动,他就知道,他就要向老板汇报 public void update(Observable observable, Object obj) { System.out.println("李斯:观察到李斯活动,开始向老板汇报了..."); this.reportToQiShiHuang(obj.toString()); System.out.println("李斯:汇报完毕,秦老板赏给他两个萝卜吃吃...\n"); try { System.out.println("我开始休眠 " + System.currentTimeMillis()); Thread.sleep(3000); System.out.println("我起来了 " + System.currentTimeMillis()); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } // 汇报给秦始皇 private void reportToQiShiHuang(String reportContext) { System.out.println("李斯:报告,秦老板!韩非子有活动了--->" + reportContext); } } public class LiuSi implements Observer { // 刘斯,观察到韩非子活动后,自己也做一定得事情 public void update(Observable observable, Object obj) { System.out.println("刘斯:观察到韩非子活动,开始动作了..."); this.happy(obj.toString()); System.out.println("刘斯:真被乐死了\n"); } // 一看韩非子有变化,他就快乐 private void happy(String context) { System.out.println("刘斯:因为" + context + ",——所以我快乐呀!"); } } public class WangSi implements Observer { // 王斯,看到李斯有活动,自己就受不了 public void update(Observable observable, Object obj) { System.out.println("Observable = " + observable.getClass() + " , obj = " + obj); System.out.println("王斯:观察到韩非子活动,自己也开始活动了..."); this.cry(obj.toString()); System.out.println("王斯:真真的哭死了...\n"); } // 一看李斯有活动,就哭,痛哭 private void cry(String context) { System.out.println("王斯:因为" + context + ",——所以我悲伤呀!"); } } public class Client { public static void main(String[] args) { // 三个观察者产生出来 Observer liSi = new LiSi(); Observer wangSi = new WangSi(); Observer liuSi = new LiuSi(); // 定义出韩非子 HanFeiZi hanFeiZi = new HanFeiZi(); // 我们后人根据历史,描述这个场景,有三个人在观察韩非子 hanFeiZi.addObserver(wangSi); hanFeiZi.addObserver(liuSi); hanFeiZi.addObserver(liSi); // 然后这里我们看看韩非子在干什么 hanFeiZi.haveBreakfast(); } }
测试结果:
韩非子:开始吃饭了...
李斯:观察到李斯活动,开始向老板汇报了...
李斯:报告,秦老板!韩非子有活动了--->韩非子在吃饭
李斯:汇报完毕,秦老板赏给他两个萝卜吃吃...
我开始休眠 1337226592828
我起来了 1337226595828
刘斯:观察到韩非子活动,开始动作了...
刘斯:因为韩非子在吃饭,——所以我快乐呀!
刘斯:真被乐死了
Observable = class _16_Observer.HanFeiZi , obj = 韩非子在吃饭
王斯:观察到韩非子活动,自己也开始活动了...
王斯:因为韩非子在吃饭,——所以我悲伤呀!
王斯:真真的哭死了...