观察者模式(又被称为发布-订阅(Publish/Subscribe)模式,属于行为型模式的一种,它定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个主题对象在状态变化时,会通知所有的观察者对象,使他们能够自动更新自己。
观察者模式结构图
在观察者模式中有如下角色:
- Subject:抽象主题(抽象被观察者),抽象主题角色把所有观察者对象保存在一个集合里,每个主题都可以有任意数量的观察者,抽象主题提供一个接口,可以增加和删除观察者对象。
- ConcreteSubject:具体主题(具体被观察者),该角色将有关状态存入具体观察者对象,在具体主题的内部状态发生改变时,给所有注册过的观察者发送通知。
- Observer:抽象观察者,是观察者者的抽象类,它定义了一个更新接口,使得在得到主题更改通知时更新自己。
- ConcrereObserver:具体观察者,实现抽象观察者定义的更新接口,以便在得到主题更改通知时更新自身的状态。
一.简单实现
抽象观察者,推模型表示会将固定的数据类型传入观察者中,耦合比较严重,拉模型则会把被观察者的引用传入观察者中,耦合较松。
两种模式的比较
■ 推模型是假定主题对象知道观察者需要的数据;而拉模型是主题对象不知道观察者具体需要什么数据,没有办法的情况下,干脆把自身传递给观察者,让观察者自己去按需要取值。
■ 推模型可能会使得观察者对象难以复用,因为观察者的update()方法是按需要定义的参数,可能无法兼顾没有考虑到的使用情况。这就意味着出现新情况的时候,就可能提供新的update()方法,或者是干脆重新实现观察者;而拉模型就不会造成这样的情况,因为拉模型下,update()方法的参数是主题对象本身,这基本上是主题对象能传递的最大数据集合了,基本上可以适应各种情况的需要。
//抽象的观察者接口 interface Observer{ public void update(Subject subject);//更新方法,传入一个Subject意味着属于拉模型,也可以传入改变的具体数据,属于推模型 }
观察者接口的具体实现
//观察者的具体实现 class ConcreteObserver implements Observer{ ConcreteObserver(Subject subject){ subject.addObserver(this);//设置被观察者 } @Override public void update(Subject subject) { System.out.println("观察者被更新了"); } }抽象主题,主题中应该含有是否改变的状态字段和观察者集合的字段,但是因为是接口,就留给实现类去实现。
主题的抽象接口 interface Subject{ public void addObserver(Observer observer);//添加观察者 public void deleteObserver(Observer observer);//删除观察者 public void notifyAllObserver();//通知所有观察者 }具体主题(具体被观察者)
//观察者的具体实现 class ConcreteObserver implements Observer{ ConcreteObserver(Subject subject){ subject.addObserver(this); } @Override public void update(Subject subject) { System.out.println("观察者被更新了"); } }测试
public class MyObserver { public static void main(String[] args) { ConcreteSubject cs = new ConcreteSubject();//注册被观察者 ConcreteObserver co = new ConcreteObserver(cs);//注册观察者 cs.setChanged(); cs.notifyAllObserver(); } }
二.利用Java类库实现
java.util包中含有一个Observer接口以及一个Observable类,可以帮助我们实现观察者模式,以下是源码,与我们的简单实现大同小异
观察者接口与我们自定义的接口差不多,多的Object参数是在Observable中重写了一个通知方法所传入的任意类型的参数。
public interface Observer { void update(Observable o, Object arg); }
而被观察者的实现则比我们自定义的接口和实现类要具体多了。但因为Java只能实现单继承,所以限制比较大。
public class Observable { private boolean changed = false; private Vector obs; /** Construct an Observable with zero Observers. */ public Observable() { obs = new Vector(); } /** * 将一个观察者添加到观察者聚集上面 */ public synchronized void addObserver(Observer o) { if (o == null) throw new NullPointerException(); if (!obs.contains(o)) { obs.addElement(o); } } /** * 将一个观察者从观察者聚集上删除 */ public synchronized void deleteObserver(Observer o) { obs.removeElement(o); } public void notifyObservers() { notifyObservers(null); } /** * 如果本对象有变化(那时hasChanged 方法会返回true) * 调用本方法通知所有登记的观察者,即调用它们的update()方法 * 传入this和arg作为参数 */ public void notifyObservers(Object arg) { Object[] arrLocal; synchronized (this) { 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(); } /** * 将“已变化”设置为true */ protected synchronized void setChanged() { changed = true; } /** * 将“已变化”重置为false */ protected synchronized void clearChanged() { changed = false; } /** * 检测本对象是否已变化 */ public synchronized boolean hasChanged() { return changed; } /** * Returns the number of observers of this <tt>Observable</tt> object. * * @return the number of observers of this object. */ public synchronized int countObservers() { return obs.size(); } }
然后对以上两个接口/抽象类进行具体实现就非常简单了
class Observer2 implements Observer { Observer2(Observable observable){ observable.addObserver(this); } @Override public void update(Observable o, Object arg) { System.out.println("一名观察者已被更新"); } } class Subject2 extends Observable{ @Override public synchronized void setChanged() { super.setChanged();//不在一个包里了,需要重写一下,父类中是protected限制 } }
测试
public class MyObserver { public static void main(String[] args) { Subject2 s2 = new Subject2(); Observer2 o2 = new Observer2(s2); s2.setChanged(); s2.notifyObservers(); } }
三.使用场景
观察者模式的主要使用场景有以下:
1.一个抽象模型有两个方面,一个方面依赖另一个方面。
2.一个对象的改变将导致一个或多个其他对象发生改变。(主要使用场景)
3.需要在系统中创建一个触发链。
四.优缺点
优点: 观察者和被观察者是抽象耦合的,可以建立一套触发机制。
缺点: 1、如果一个被观察者对象有很多的直接和间接的观察者的话,将所有的观察者都通知到会花费很多时间。如果顺序执行,某一观察者错误会导致系 统卡壳,所以一般采用异步方式 。
2、如果在观察者和观察目标之间有循环依赖的话,观察目标会触发它们之间进行循环调用,可能导致系统崩溃,所以要避免循环引用。
3、观察者模式没有相应的机制让观察者知道所观察的目标对象是怎么发生变化的,而仅仅只是知道观察目标发生了变化。