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 cbf4life@126.com 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 = 韩非子在吃饭
王斯:观察到韩非子活动,自己也开始活动了...
王斯:因为韩非子在吃饭,——所以我悲伤呀!
王斯:真真的哭死了...