设计模式|设计模式 -- 观察者模式 (Observer Pattern)

定义:定义对象间一种一对多的依赖关系,使得每当一个对象改变状态时,则所有依赖于它的对象都会得到通知并被自动更新。
通俗来讲,观察者模式就是满足这样的需求:如果一个对象的状态发生改变,某些与它相关的对象也要随之做出相应的变化。
场景描述:在班级上, 班长管理好班级的纪律,若有同学违反纪律,班长就会向班主任汇报情况;在这件事情中,班级同学是被观察者,班长是观察者,班级同学若违反了课堂纪律(说明对象状态改变),那么班长就会做出对应的措施(向班主任汇报情况)。
观察者通用类图:
设计模式|设计模式 -- 观察者模式 (Observer Pattern)
文章图片
观察者模式.png
Subject(被观察者):一般来讲为抽象类,定义被观察者必须实现的职责,它必须能够动态的添加,删除观察者。相当于场景描述中的班级同学,可以选择多个班长(观察者)。
Observer(观察者):一般来讲为接口,观察者收到被观察者送来的消息后,会自动进行更新操作,对消息进行处理。相当于场景描述中的班长,处理行为就是向班主任汇报情况。
ConcreteSubject(被观察者实现类):实现被观察者接口定义的抽象方法。
ConcreteObserver(观察者实现类):实现观察者接口定义的抽象方法。
Subject(被观察者)代码:

public abstract class Subject { private Vector observer1Vector = new Vector(); // 定义一个集合,用于装观察者。 public void addObserver(Observer1 observer1){ // 添加观察者 this.observer1Vector.add(observer1); } publicvoid 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 方法的应用程序,让所有对象的观察者都知道该变化。 * @paramoobservable 对象. * @paramarg传递给 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 obs; //使用Vector集合装载所有的观察者,默认为空/**构建一个观察者对象为零个的被观察者集合对象*/ public Observable() { obs = new Vector<>(); }/** * 添加观察者对象的方法,只要非空且与已经存在集合中的观察者不一样,那么就被添加进来 * @paramo一个添加进来的观察者对象 * @throws NullPointerExceptionif 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值进来,此方法将不会起作用 * @paramo被删除的观察者对象 */ 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)方法,决定是否通知观察者。
【设计模式|设计模式 -- 观察者模式 (Observer Pattern)】参考书籍:设计模式之禅 --- 秦小波 著

    推荐阅读