观察者(Observer)模式

  观察者模式又叫做发布-订阅模式(Publish.Subscribe)模式、模型-视图模式(Model/View)模式、源-监听器模式(Source/Listener)模式或从属者(Dependents)模式。

  观察者模式定义了一种一对多的依赖关系,让多个观察者同时监听某一个主题对象。这个主题对象在状态上发生变化时,会通知所有观察者对象,使它们能够自动更新自己。

 1.  观察者模式结构

 一个简单的观察者模型如下:

观察者(Observer)模式

角色:

抽象(Subject)主题角色:把所有的观察者维持在一个集合中,每个主题都可以有任意数量的观察者。提供一个接口,可以增加和删除观察者,主题角色又叫做被观察者(Observable)。

抽象观察者(Observer)角色:在得到主题的通知时更新自己。有时候观察者依赖于被观察者,可以将update方法修改为 void update(Subject subject)。

具体主题角色:维护所有的观察者,在具体主题的内部状态改变时给所有登记的观察者发送通知。

具体观察者角色:存储与主题的状态自恰的状态,也就是随着主题的状态改变自己的状态。

代码如下:

package cn.qlq.observer;

public interface Subject {
    void attach(Observer observer);

    void delete(Observer observer);

    void notifyObservers();
}
package cn.qlq.observer;

import java.util.Enumeration;
import java.util.List;
import java.util.Vector;

public class ConcreteSubject implements Subject {

    private List<Observer> observers = new Vector<>();

    @Override
    public void attach(Observer observer) {
        observers.add(observer);
    }

    @Override
    public void delete(Observer observer) {
        observers.remove(observer);
    }

    @Override
    public void notifyObservers() {
        for (Observer observer : observers) {
            observer.update();
        }
    }

}
package cn.qlq.observer;

public interface Observer {

    /**
     * 
     */
    void update();

}
package cn.qlq.observer;

public class ConcreteObserver implements Observer {

    @Override
    public void update() {
        System.out.println(" i am notified");
    }

}

2.  第二种实现

  考虑上面的主题中,管理维护观察者集合的方法可以放到抽象类中去实现,因此可以将维护观察者关系的代码抽取到抽象类中,类图如下:

观察者(Observer)模式

  这种方式与上面的区别是代表存储观察者对象的集合从连线是从抽象主题到抽象观察者。(也就是抽象主体维护抽象观察者的引用关系)

代码如下:

package cn.qlq.observer;

import java.util.List;
import java.util.Vector;

public abstract class Subject {
    private List<Observer> observers = new Vector<>();

    public void attach(Observer observer) {
        observers.add(observer);
    }

    public void delete(Observer observer) {
        observers.remove(observer);
    }

    public void notifyObservers() {
        for (Observer observer : observers) {
            observer.update();
        }
    }
}
package cn.qlq.observer;

public class ConcreteSubject extends Subject {

    private String state;

    public void changeState(String newState) {
        state = newState;
        this.notifyObservers();
    }

}
package cn.qlq.observer;

public interface Observer {

    /**
     * 
     */
    void update();

}
package cn.qlq.observer;

public class ConcreteObserver implements Observer {

    @Override
    public void update() {
        System.out.println(" i am notified");
    }

}

客户端测试代码:

package cn.qlq.observer;

public class Client {

    public static void main(String[] args) {
        ConcreteSubject subject = new ConcreteSubject();
        Observer observer = new ConcreteObserver();

        subject.attach(observer);

        subject.changeState("1");
    }

}

3.  Java语言对观察者模式的支持

  在Java.util保重中,提供了一个Observable类以及一个Observer接口。

Observer接口:  此接口只定义了一个update方法,当被观察者的状态发生变化时被观察者对象的notifyOeservers()方法会调用这一方法。

public interface Observer {

    void update(Observable o, Object arg);
}

Observable类:被观察者类都是该类的子类,该类有两个重要的方法:

  setChanged():  设置一个内部标记标记其状态发生变化

  notifyObsers(): 这个方法被调用时会调用所有注册的观察者的update()方法。

package java.util;

public class Observable {
    private boolean changed = false;
    private Vector 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); } 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(); } }

简单的使用Java对观察者模式的支持:

package cn.qlq.observer;

import java.util.Observable;

public class Watched extends Observable {

    private String data = "";

    public String getData() {
        return data;
    }

    public void changeData(String data) {
        if (!this.data.equals(data)) {
            this.data = data;
            setChanged();
        }

        notifyObservers();
    }

}
package cn.qlq.observer;

import java.util.Observable;
import java.util.Observer;

public class Watcher implements Observer {

    @Override
    public void update(Observable o, Object arg) {
        if (o != null && (o instanceof Watched)) {
            Watched watched = (Watched) o;
            System.out.println("data changed to: " + watched.getData());
        }
    }

}

客户端代码

package cn.qlq.observer;

public class Client {

    public static void main(String[] args) {
        Watched watched = new Watched();
        Watcher watcher = new Watcher();

        watched.addObserver(watcher);

        watched.changeData("123");
        watched.changeData("123");
        watched.changeData("456");
        watched.changeData("789");
    }
}

结果:(虽然改变了四次值,但是有两次一样,查看源码啊在notifyObsers()中会清掉changed的值)

data changed to: 123
data changed to: 456
data changed to: 789

4.  观察者模式优缺点

优点:

(1)观察者模式在被观察者和观察者直接建立一个抽象的耦合

(2)观察者模式支持广播通信。被观察者会向所有登记的观察者发出通知。

缺点:

(1)如果被观察者的观察者过多,通知所有观察者需要花费很多时间

(2)如果在被观察者之间有循环依赖容易循环调用

(3)如果对观察者的通知是通过多线程通知必须保证通知的正确性

(4)观察者可以知道观察者状态发生了变化,不知道是怎么发生变化的。

总结:

意图:定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。

主要解决:一个对象状态改变给其他对象通知的问题,而且要考虑到易用和低耦合,保证高度的协作。

何时使用:一个对象(目标对象)的状态发生改变,所有的依赖对象(观察者对象)都将得到通知,进行广播通知。

如何解决:使用面向对象技术,可以将这种依赖关系弱化。

关键代码:在抽象类里有一个 ArrayList 存放观察者们。

应用实例: 1、拍卖的时候,拍卖师观察最高标价,然后通知给其他竞价者竞价。 2、西游记里面悟空请求菩萨降服红孩儿,菩萨洒了一地水招来一个老乌龟,这个乌龟就是观察者,他观察菩萨洒水这个动作。

优点: 1、观察者和被观察者是抽象耦合的。 2、建立一套触发机制。

缺点: 1、如果一个被观察者对象有很多的直接和间接的观察者的话,将所有的观察者都通知到会花费很多时间。 2、如果在观察者和观察目标之间有循环依赖的话,观察目标会触发它们之间进行循环调用,可能导致系统崩溃。 3、观察者模式没有相应的机制让观察者知道所观察的目标对象是怎么发生变化的,而仅仅只是知道观察目标发生了变化。

注意事项: 1、JAVA 中已经有了对观察者模式的支持类。 2、避免循环引用。 3、如果顺序执行,某一观察者错误会导致系统卡壳,一般采用异步方式。