什么是观察者模式
在对象之间定义一对多的依赖,这样依赖,当一个对象改变状态,依赖它的对象都会收到通知,并自动更新。
在观察者模式中,分为推模型和拉模型两种
两种模式的比较
■推模型是假定主题对象知道观察者需要的数据;而拉模型是主题对象不知道观察者具体需要什么数据,没有办法的情况下,干脆把自身传递给观察者,让观察者自己去按需要取值。
■推模型可能会使得观察者对象难以复用,因为观察者的update()方法是按需要定义的参数,可能无法兼顾没有考虑到的使用情况。这就意味着出现新情况的时候,就可能提供新的update()方法,或者是干脆重新实现观察者;而拉模型就不会造成这样的情况,因为拉模型下,update()方法的参数是主题对象本身,这基本上是主题对象能传递的最大数据集合了,基本上可以适应各种情况的需要。
例子:
推模型
//主题接口 public interface Subject { public void registerObserver(Observer o); public void removeObserver(Observer o); public void notifyObservers(); }
//具体主题类 public class ConcreteSubject implements Subject { //保存注册的观察者对象 private ArrayList observers; private String state; public ConcreteSubject() { observers = new ArrayList(); } //注册观察者对象 public void registerObserver(Observer o) { observers.add(o); } //删除观察者对象 public void removeObserver(Observer o) { int i = observers.indexOf(o); if(i >= 0) { observers.remove(o); } } //通知所有观察者对象 public void notifyObservers() { for(int i=0; i<observers.size(); i++) { Observer observer = (Observer) observers.get(i); observer.update(state); } } //当state值修改时,调用该方法 public void measurementsChanged() { notifyObservers(); } //修改state public void setState(String state) { this.state = state; measurementsChanged(); } }
//抽出显示行为作为一个接口 public interface DisplayElement { public void display(); }
//观察者接口 public interface Observer { public void update(String state); }
//具体观察者类 public class ConcreteObserver implements Observer, DisplayElement { private String state; //更新数据 public void update(String state) { this.state = state; display(); } public void display() { System.out.println("ConcreteObserver state: " + state); } }
//测试类 public class ObserverTest { public static void main(String[] args) { //创建主题对象 ConcreteSubject subject = new ConcreteSubject(); //创建观察者对象 ConcreteObserver observer = new ConcreteObserver(); //为观察者对象注册主题 subject.registerObserver(observer); //修改state的值 subject.setState("100"); } }
控制台输出:ConcreteObserver state: 100
用内置的支持实现观察者模式
Observable源码
package java.util; public class Observable { private boolean changed = false; private Vector<Observer> obs; 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); } //如果changed为true,通知所有注册了的观察者 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(); } protected synchronized void setChanged() { changed = true; } protected synchronized void clearChanged() { changed = false; } public synchronized boolean hasChanged() { return changed; } public synchronized int countObservers() { return obs.size(); } }
//具体主题类 public class ConcreteSubject extends Observable{ private String state; //当state值修改时,调用该方法 public void measurementsChanged() { //调用notifyObservers()方法前,需调用setChanged()来指示状态已经改变 setChanged(); //这里的notifyObservers()方法并没有传送数据对象 notifyObservers(); } //修改state public void setState(String state) { this.state = state; measurementsChanged(); } public String getState() { return state; } }
//具体观察者类 public class ConcreteObserver implements Observer, DisplayElement { private String state; //更新数据 public void update(Observable obj, Object arg) { if(obj instanceof ConcreteSubject) { ConcreteSubject concreteSubject = (ConcreteSubject) obj; state = concreteSubject.getState(); display(); } } public void display() { System.out.println("ConcreteObserver state: " + state); } }
//抽出显示行为作为一个接口 public interface DisplayElement { public void display(); }
两种模式的比较
■ 推模型是假定主题对象知道观察者需要的数据;而拉模型是主题对象不知道观察者具体需要什么数据,没有办法的情况下,干脆把自身传递给观察者,让观察者自己去按需要取值。■ 推模型可能会使得观察者对象难以复用,因为观察者的update()方法是按需要定义的参数,可能无法兼顾没有考虑到的使用情况。这就意味着出现新情况的时候,就可能提供新的update()方法,或者是干脆重新实现观察者;而拉模型就不会造成这样的情况,因为拉模型下,update()方法的参数是主题对象本身,这基本上是主题对象能传递的最大数据集合了,基本上可以适应各种情况的需要。
参考:
《Head First 设计模式》