浅尝设计模式——如何使用观察者模式

本文为阅读《Head First 设计模式》一书的摘要总结

观察者模式

概念

定义

观察者 模式定义了对象之间的 一对多 依赖,这样一来,当一个对象改变状态时,它的所有依赖都会受到通知并自动更新。

类图

浅尝设计模式——如何使用观察者模式_第1张图片

松耦合

观察者模式提供了一种对象设计,让主题和观察者之间松耦合。

主题唯一依赖的东西是一个实现Observer接口的 对象列表。当有新类型的观察者出现是,不需要改变主题代码,我们只需要新类型实现Observer接口,并注册为主题的观察者(加入对象列表)。当改变主题时(对自身状态操作的改变),只要它还实现Subject接口,观察者也不需要改变。所以它们之间是松耦合的。

示例

本示例的功能是气象站将温度、湿度和气压数据发送给不同的展示牌。

主题:

public interface Subject {
     
    void registryObserver(Observer observer);
    void removeObserver(Observer observer);
    void notifyObserver();
}

public class WeatherData implements Subject{
     

    private List<Observer> observers;
    private float temperature;
    private float humidity;
    private float pressure;
    public WeatherData() {
     
        observers = new ArrayList<>();
    }

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

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

    @Override
    public void notifyObserver() {
     
        for(Observer o : observers){
     
            o.update(temperature,humidity,pressure);
        }
    }

    public void measurementsChanged(){
     
        notifyObserver();
    }
	//该方法用来模拟一次测量
    public void setMeasurements(float temperature,float humidity,float pressure){
     
        this.temperature = temperature;
        this.humidity = humidity;
        this.pressure = pressure;
        measurementsChanged();
    }
}

观察者:

public interface Observer {
     
    void update(float temp, float humidity, float pressure);
}
public interface DisplayElement {
     
    void display();
}
public class CurrentConditionsDisplay implements Observer, DisplayElement{
     
    private float temperature;
    private float humidity;
    private float pressure;
    private Subject weatherData;

    public CurrentConditionsDisplay(Subject weatherData) {
     
        this.weatherData = weatherData;
        weatherData.registryObserver(this);
    }

    @Override
    public void display() {
     
        System.out.println("CurrentDisplay:" + temperature + "F degrees and " + humidity + "% humidity");
    }

    @Override
    public void update(float temperature, float humidity, float pressure) {
     
        this.temperature = temperature;
        this.humidity = humidity;
        this.pressure = pressure;
        display();
    }
}

其类图如下:
浅尝设计模式——如何使用观察者模式_第2张图片

在这个示例中,主题直接将自己的状态(温度、湿度和气压)传入观察者,这种方式起始并不够好。若是以后主题有新的状态加入,那么主题和观察者代码都需要修改。

使用Java内置的观察者模式

java.util包下含有基本的Observable Observer 接口。这跟我们之前定义的Subject接口与Observer接口十分相似。但是使用Java内置的Observable类和Observer接口,我们可以使用 推(push)拉(pull) 的方式来传送数据。

Observable类的方法:

浅尝设计模式——如何使用观察者模式_第3张图片

Observer接口定义的方法:

浅尝设计模式——如何使用观察者模式_第4张图片

现在我们来重构上面的代码:

推数据

可观察者:

public class WeatherData extends Observable {
     

    private List<Observer> observers;
    private Data data;
    public WeatherData() {
     
        observers = new ArrayList<>();
        data = new Data();
    }
    public void measurementsChanged(){
     
        setChanged();
        notifyObservers(data);//将数据推送给观察者
    }
    public void setMeasurements(float temperature,float humidity,float pressure){
     
        data.setTemperature(temperature);
        data.setHumidity(humidity);
        data.setPressure(pressure);
        measurementsChanged();
    }
}

在调用notifyObservers方法之前,我们调用了setChanged方法,这是必要的,我们看一下源码中notifyObserverssetChaged方法的实现:

public class Observable {
     
 	private boolean changed = false;
    private Vector<Observer> obs;
	protected synchronized void setChanged() {
     
	    changed = true;
	}
	public void notifyObservers(Object arg) {
     
	    Object[] arrLocal;
	    synchronized (this) {
     
	        if (!changed)//只有changed为true时,才会通知观察者
	            return;
	        arrLocal = obs.toArray(); //obs是订阅了该主题的观察者列表
	        clearChanged();
	    }
	    for (int i = arrLocal.length-1; i>=0; i--)
	        ((Observer)arrLocal[i]).update(this, arg);
	}
	protected synchronized void clearChanged() {
     
	    changed = false;
	}
	...
}

有源码可值,只有changed为真时,才会通知观察者。changed标志是十分有用的,setChanged方法可以让我们在更新观察者是,有跟多的弹性,我们可以更适当的通知观察者。比如我们制定一定的规则,只有当满足条件时,我们才调用setChanged方法,进行有效的更新。

若是我们使用的是notifyObservers(Object arg)方法,那么就采用了 数据的方式。

观察者:

public class CurrentConditionsDisplay implements java.util.Observer, DisplayElement{
     
    private Observable weatherData;

    public CurrentConditionsDisplay(Observable weatherData) {
     
        this.weatherData = weatherData;
        weatherData.addObserver(this);
    }

    @Override
    public void display(Data data) {
     
        System.out.println("CurrentDisplay:" + data.getTemperature() + "F degrees and " + data.getHumidity() + "% humidity");
    }

    @Override
    public void update(Observable o, Object arg) {
     
        if (o instanceof WeatherData){
     
            display((Data)arg);
        }
    }
}

java.util.Observer接口定义的update接受两个参数:一个Observable对象,供我们判断是哪个可观察者送出了通知;一个Object对象,这对应于notifyObservers(Object arg)中的arg

拉数据

以上是推数据,我们再做一点改变,就可以实现观察者自己拉数据:

可观察者:

public class WeatherData extends Observable {
     

    private List<Observer> observers;
    private Data data;
    public WeatherData() {
     
        observers = new ArrayList<>();
        data = new Data();
    }

    public Data getData() {
     
        return data;
    }

    public void measurementsChanged(){
     
        setChanged();
        notifyObservers();
    }
    public void setMeasurements(float temperature,float humidity,float pressure){
     
        data.setTemperature(temperature);
        data.setHumidity(humidity);
        data.setPressure(pressure);
        measurementsChanged();
    }
}

我们为可观察者的状态data添加了访问器,并且在通知观察者时,调用notifyObservers()方法,没有传送具体的数据给观察者。

观察者:

public class CurrentConditionsDisplay implements java.util.Observer, DisplayElement{
     
    private Observable weatherData;

    public CurrentConditionsDisplay(Observable weatherData) {
     
        this.weatherData = weatherData;
        weatherData.addObserver(this);
    }

    @Override
    public void display(Data data) {
     
        System.out.println("CurrentDisplay:" + data.getTemperature() + "F degrees and " + data.getHumidity() + "% humidity");
    }

    @Override
    public void update(Observable o, Object arg) {
     
        if (o instanceof WeatherData){
     
            Data data = (Data) arg;
            if (data == null){
     
                WeatherData weatherData = (WeatherData)o;
                data = weatherData.getData();//拉取数据
            }
            display(data);
        }
    }
}

观察者这边的update方法中,判断arg为空,那么就主动调用访问器去拉取数据。

使用Java内置的Observable类带来的弊端:

Observable是一个类,这违背了 针对接口编程,而非针对实现编程,这导致我们在使用它时,必须设计一个类继承它。如果某个类想同时具有Observable和另一个超类的行为,就会陷入两难。这限制了Observalbe的复用能力。另外,因为没有Observalbe接口,我们也不能建立自己的实现,和Java内置的ObserverAPI搭配使用。再者Observable.setChanged被修饰为protected,所以我们将Observable实例组合到我们自己的对象中来后,无法调用setChanged方法来正常通知观察者。

你可能感兴趣的:(设计模式)