定义:定义对象间一种一对多的依赖关系,使得每当一个对象改变状态时,则所有依赖于它的对象都会得到通知并被自动更新。
通俗来讲,观察者模式就是满足这样的需求:如果一个对象的状态发生改变,某些与它相关的对象也要随之做出相应的变化。
场景描述:在班级上, 班长管理好班级的纪律,若有同学违反纪律,班长就会向班主任汇报情况;在这件事情中,班级同学是被观察者,班长是观察者,班级同学若违反了课堂纪律(说明对象状态改变),那么班长就会做出对应的措施(向班主任汇报情况)。
观察者通用类图:
Subject(被观察者):一般来讲为抽象类,定义被观察者必须实现的职责,它必须能够动态的添加,删除观察者。相当于场景描述中的班级同学,可以选择多个班长(观察者)。
Observer(观察者):一般来讲为接口,观察者收到被观察者送来的消息后,会自动进行更新操作,对消息进行处理。相当于场景描述中的班长,处理行为就是向班主任汇报情况。
ConcreteSubject(被观察者实现类):实现被观察者接口定义的抽象方法。
ConcreteObserver(观察者实现类):实现观察者接口定义的抽象方法。
Subject(被观察者)代码:
public abstract class Subject {
private Vector<Observer1> observer1Vector = new Vector<Observer1>(); // 定义一个集合,用于装观察者。
public void addObserver(Observer1 observer1){ // 添加观察者
this.observer1Vector.add(observer1);
}
public void subObserver(Observer1 observer1){ // 删除观察者
this.observer1Vector.remove(observer1);
}
public void notifyObserver(){ //通知所有的观察者
for (Observer1 observer1 : observer1Vector){
observer1.update();
}
}
public abstract void doSomething(); //处理业务逻辑
}
ConcreteObserver(观察者实现类)代码:
public class ConcreteSubject extends Subject {
@Override
public void doSomething() {
System.out.println("班长准备向班主任汇报情况");
this.notifyObserver(); //通知所有观察者
}
}
Observer(观察者)代码:
public interface Observer1 {
public void update();
}
ConcreteObserver(观察者实现类)代码:
public class ConcreteObserver11 implements Observer1 {
@Override
public void update() {
System.out.println("班主任收到情况信息");
}
}
客户端代码:
public class Client {
public static void main(String[] args) {
Subject subject = new ConcreteSubject();
subject.addObserver(new ConcreteObserver11());
subject.doSomething();
}
}
----------------output------------------
班长准备向班主任汇报情况
班主任收到情况信息
观察者模式的优点:
①观察者和被观察者之间是抽象耦合的,不管是增加观察者还是被观察者都是容易扩展的。
②建立一套触发机制,将所有的类串联起来,形成触发链。如上述类图中,被观察者有变动会自动触发观察者行为,如此串联下去。
观察者模式的缺点:
①观察者模式所建立起来的串联触发机制,会严重制约系统的执行效率,因此一般会考虑异步的方式。
②一个被观察者,多个观察者的情况下,开发调试会比较复杂,给开发效率带来影响。
观察者模式的使用场景
①在行为可拆分的各类间进行关联的模式下。
②事件多级触发模式。
③跨系统的消息交换场景,如消息队列的处理机制。
观察者模式的注意事项
①由于串联触发机制的低效率,在观察者模式中建议最多出现一个对象既是被观察者也是观察者,也就是消息最多被转发一次。
②与责任链模式相比,观察者广播链在传播的过程中消息是可以改变的,它是由两个相邻节点协商的消息结构;而责任链模式在消息传递过程中基本保持消息不变,如要改变也只能在原有的消息上进行修正。
③在观察者数量较多,处理时间较慢的情况下,需使用异步,这样就需要考虑线程安全与队列的问题。
Java世界中的观察者模式
在Java中有一个类与接口就提供了观察者模式中被观察者与观察者的角色:java.util.Observable 与 java.util.Observer,接下来我们分析这两个类源码并使用。
接口 java.util.Observer :观察者身份
public interface Observer {
/**
* 只要观察对象发生变化,就会调用此方法.
* 一个称为Observable对象的 notifyObservers 方法的应用程序,让所有对象的观察者都知道该变化。
* @param o observable 对象.
* @param arg 传递给 notifyObservers 方法的参数。
*/
void update(Observable o, Object arg);
}
类 java.util.Observable:被观察者身份
/**
* 这个类代表一个被观察的对象或模型视图范例中的“数据”,它可以被分类为表示应用程序想要观察的对象
* 被观察对象有一个或者多个观察者。观察者是可以通过实现接口 java.util.Observer 的任何对象
* 在一个类 java.util.Observable 的实例状态发生改变后,可以通过调用 Observable 的notifyObservers方法通知给所有的观察者
* 并没有指定通知的发送顺序,Observable类中提供的默认实现将按其注册顺序通知观察者,但子类可能会更改此顺序
* 在单线程中传递通知,可以保证它们的子类按照选择的顺序进行通知
* 请注意,此通知机制与线程无关,并且与类 Object 的 wait,notify 机制完全分离
* 新创建的被观察对象,其观察者集合为空,当且仅当 equals 方法返回值为true时,两个观察者被认为是相同的
*/
public class Observable {
private boolean changed = false; //判断 被观察者状态是否改变,默认值为false
private Vector<Observer> obs; //使用Vector集合装载所有的观察者,默认为空
/**构建一个观察者对象为零个的被观察者集合对象*/
public Observable() {
obs = new Vector<>();
}
/**
* 添加观察者对象的方法,只要非空且与已经存在集合中的观察者不一样,那么就被添加进来
* @param o 一个添加进来的观察者对象
* @throws NullPointerException if the parameter o is null.
*/
public synchronized void addObserver(Observer o) {
if (o == null)
throw new NullPointerException();
if (!obs.contains(o)) {
obs.addElement(o);
}
}
/**
* 从该对象的观察者集合中删除观察者
* 如果传递一个null值进来,此方法将不会起作用
* @param o 被删除的观察者对象
*/
public synchronized void deleteObserver(Observer o) {
obs.removeElement(o);
}
/**
* 如果这个对象发生改变,如 hasChanged 方法所示,则通知所有观察者,然后调用 clearChanged 方法表明这个对象不再改变
* 每个观察者都使用两个参数来调用其 update 方法:此被观察者对象和null,换句话说,这个方法相当于:notifyObservers(null)
*/
public void notifyObservers() {
notifyObservers(null);
}
/**
* 如果这个对象发生改变,如 hasChanged 方法所示,则通知它的所有观察者,然后调用 clearChanged 方法表明这个对象不再改变
* 每个观察者都使用两个参数来调用其 update 方法:此被观察者对象和 arg 参数
*/
public void notifyObservers(Object arg) {
Object[] arrLocal; // 临时数组缓冲区,用作当前观察者状态的快照
synchronized (this) {
/*
* 我们不希望观察者在持有自己的监视器时将回调变为任意代码。
* 我们从Vector中提取的每个Observable并存储 Observer 的状态的代码需要同步,但是通知观察者时不会。
* 竞争条件下最糟糕的情况:①最近添加的观察者将错过正在进行的通知 ②最近未注册的观察者将在未被关心时被错误的通知
*/
if (!changed) //如果被观察者状态没有改变
return; //直接返回
arrLocal = obs.toArray(); //否则取得当前观察者的状态快照
clearChanged(); //清楚改变状态,恢复为默认状态值
}
for (int i = arrLocal.length-1; i>=0; i--) //从集合的最后一个开始倒序通知观察者
((Observer)arrLocal[i]).update(this, arg);
}
/**
* 清除观察者列表,以便该对象不再有任何观察者。
*/
public synchronized void deleteObservers() {
obs.removeAllElements();
}
/**
* 将这个被观察者标记为已被更改状态,hasChanged 方法现在将返回true
*/
protected synchronized void setChanged() {
changed = true;
}
/**
* 表明这个对象不再被改变,或者它已经通知了它所有的观察者它的最近的改变,所以hasChanged 方法现在将返回false
* 此方法由 notifyObservers 方法自动调用
*/
protected synchronized void clearChanged() {
changed = false;
}
/**
* 测试此对象是否已被更改,默认返回false
* 当且仅当 setChanged 方法比 clearChanged 方法更近期被调用时返回 true,否则返回 false
*/
public synchronized boolean hasChanged() {
return changed;
}
/**
* 返回这个对象的观察者数量
*/
public synchronized int countObservers() {
return obs.size();
}
}
使用 java.util.Observable 与 java.util.Observer 演示上面的场景描述
//创建一个同学的接口,里面有吃饭与娱乐两个行为
public interface IClassMate {
public void haveBreakfast();
public void haveFun();
}
//实现类实现上面的两个方法,同时继承 java.util.Observable 扮演被观察者角色
public class ClassMate extends Observable implements IClassMate {
@Override
public void haveBreakfast() {
System.out.println("班长看到同学开始吃饭,开始向老师汇报");
super.setChanged();
super.notifyObservers("老师!!!同学吃饭去了");
}
@Override
public void haveFun() {
System.out.println("班长看同学开始玩,开始向老师汇报");
super.setChanged();
super.notifyObservers("老师!!!同学玩去了");
}
}
//创建班主任类,实现 java.util.Observer,扮演观察者角色
public class Teacher implements Observer {
@Override
public void update(Observable o, Object arg) {
System.out.println("老师收到班长的信息"+arg.toString());
}
}
//客户端演示
public class Client {
public static void main(String[] args) {
IClassMate classMate = new ClassMate(); //创建一个被观察者
Observer teacher = new Teacher(); //创建一个观察者
((ClassMate) classMate).addObserver(teacher); //被观察者添加观察者
classMate.haveBreakfast(); //被观察者行为触发
classMate.haveFun();
}
}
--------------output------------------
班长看到同学开始吃饭,开始向老师汇报
老师收到班长的信息:老师!!!同学吃饭去了
班长看同学开始玩,开始向老师汇报
老师收到班长的信息:老师!!!同学玩去了
在真实项目中的观察者模式改造
①观察者与被观察者之间的消息沟通
被观察者状态改变会触发观察者的一个行为,同时会传递一个消息给观察者。在实际项目中,观察者的update()方法接受两个参数,一个是被观察者对象,一个是DTO(数据传输对象),DTO一般是JavaBean,由被观察者生成,由观察者消费。
在远程传输中,一般是以XML格式传输。
②观察者的响应方式
观察者包含复杂的逻辑,不仅需要接收来自被观察者的消息,还需要对它们进行逻辑处理,在一个观察者多个被观察者情况下,若观察者消耗时间过长,那么被观察者的时间是不是也相应的延长了呢?这时就需要考虑性能优化。
有两个方法:
(1)采用多线程技术,也就是大家说的异步架构。
(2)缓存技术,提供足够的资源,保证快速响应。
③被观察者尽量自己做主
被观察者的状态改变是否一定要通知给观察者呢?这不一定,得看设计的具体要求。一般情况下,会对被观察者的业务逻辑doSomething方法实现重载,然后增加一个doSomething(boolean isNoitfyObs)方法,决定是否通知观察者。
参考书籍:设计模式之禅 --- 秦小波 著