《Head First 设计模式》读书笔记——观察者模式

	QWA`第二篇写观察者模式,也是书里的第二章,说到这个观察者模式,印象还是比较深刻的,因为去年软考软件设计师最后一道题,考的就是这个观察者模式。

概念

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

示例说明

需要做一个系统,这个系统包括三部分,气象站(获取实际气象数据的物理装置)、WeatherData 对象(追踪来自气象站的数据,并更新布告板)和布告板(显示目前天气状况给用户看)。

本例中,WeatherData 对象知道如何跟物理气象站联系,以取得更新的数据。WeatherData 对象还会更新三个布告板的显示:目前状况(温度、湿度、气压)、气象统计和天气预报。

我们的目标是建立一个应用,利用 WeatherData 对象取得数据,然后更新三个布告板。

WeatherData 类包括的方法如下。

getTemplate()
getHumidity()
getPressure()
measuermentsChanged()

我们需要实现 measurementChanged() 方法

  • 当新的测量数据备妥时,这个方法就会被调用
  • 当 WeatherData 有新的测量数据,三块布告板必须马上更新
  • 此系统必须可扩展,让其他开发人员建立定制的布告板,用户可添加或删除任何布告板,现有三个布告板,目前状况、气象统计以及天气预报布告板。

错误示范

首先来一个错误示范啊

public class WeatherData {
    /**
     * 目前状况布告板
     */
    CurrentConditionDisplay currentConditionDisplay;
    /**
     * 气象统计布告板
     */
    ForecastDisplay forecastDisplay;
    /**
     * 天气预报布告板
     */
    StatisticsDisplay statisticsDisplay;
    float temperature;
    float humidity;
    float pressure;

    public float getHumidity() {
        return humidity;
    }

    public float getPressure() {
        return pressure;
    }

    public float getTemperature() {
        return temperature;
    }

    public void measurementsChanged() {
        float temperature = getTemperature();
        float humidity = getHumidity();
        float pressure = getPressure();

        currentConditionDisplay.update(temperature, humidity, pressure);
        statisticsDisplay.update(temperature, humidity, pressure);
        forecastDisplay.update(temperature, humidity, pressure);
    }
}

这个类里面声明了几个实例,然后在 measurementsChanged() 方法里进行布告板的更新,当数据有了更新以后,会调用这个方法对布告板进行更新。

但这个方法弊病大了,它是针对具体实现编程,以后增加或删除布告板时必须修改程序。所以这个设计肯定是不可以用的,太耗费资源,并且需要反复修改代码。

修改

现在我们就可以聊到正题了,「观察者模式=主题+观察者」,那么什么是主题,什么是观察者?

主题,就是管理数据的对象,相当于监测中心,当数据有变化的时候,主题对象第一时间会通知观察者对象。

观察者对象注册到主题,当数据有变化的时候,会接收到主题的数据更新。就类似于报社和读者之间的关系一样,当有新的新闻需要发表的时候,报社印刷报纸,派送到读者手中。这是一对多的关系,当一个对象改变时,其他的依赖者都会收到通知。

设计原则:为了交互对象之间的松耦合设计而努力。

对于观察者对象,主题只知道观察者实现了某个接口(Observer接口),主题不需要知道观察者的具体类是谁,做了些什么或其他任何细节。而且观察者可以随意增减,而并不会受到影响。有新的观察者出现时,主题代码不用修改,需要做的就是在新类中实现此观察者接口,然后注册为观察者。

观察者模式就是为了让主题和观察者松耦合,他们可以进行交互,但不清楚彼此的细节。改变他们两者之间的任何一方,都不会影响另一方,因为两者是松耦合的,所以只要他们之间的接口仍被遵守,我们就可以*地改变他们。

接下来就看看具体的代码吧。

代码实现示例

首先是三个接口,分别是 DisplayElement、Observer 和 Subject 三个接口,顾名思义,第一个接口用于布告板展示,第二个接口是观察者接口,用以进行数据的更新,第三个接口是主题接口,用来注册观察者。

public interface DisplayElement {
    public void display();
}

public interface Observer {
    public void update(float temp, float humidity, float pressure);
}

public interface Subject {
    public void registerObserver(Observer observer);
}

接下来是我们之前提到过的,WeatherData 类,这个类由于需要通知观察者进行数据更新,所以它是主题类,需要实现 Subject 接口,包含一个观察者的集合。

public class WeatherData implements Subject {
    private ArrayList observers;
    private float temperature;
    private float humidity;
    private float pressure;

    public WeatherData() {
        observers = new ArrayList();
    }

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

    public void removeObserver(Observer observer) {
        int i = observers.indexOf(observer);
        if (i >= 0) {
            observers.remove(observer);
        }
    }

    public void notifyObservers() {
        for (int i = 0; i < observers.size(); i++) {
            Observer observer = (Observer) observers.get(i);
            observer.update(temperature, humidity, pressure);
        }
    }

    public void measurementsChanged() {
        notifyObservers();
    }

    public void setMeasurements(float temperature, float humidity, float pressure) {
        this.temperature = temperature;
        this.humidity = humidity;
        this.pressure = pressure;
        measurementsChanged();
    }
}

observers 是观察者的集合,在构建 Weather 对象时同时实例化一个空的集合,用以做注册观察者对象的容器,其余三个属性是布告板需要的属性。

registerObserver(Observer observer) 方法是注册观察者的方法,同理 removeObserver(Observer observer) 方法是删除观察者的方法。

notifyObservers() 方法就是遍历集合通知观察者数据有更新,measurementsChanged() 方法就是前文提到的更新数据的方法。

setMeasurements(float temperature, float humidity, float pressure)(float temperature, float humidity, float pressure) 方法是设置那三个属性的方法,同时调用measurementsChanged() 方法通知布告板进行数据更新。

接下来就是布告板 CurrentConditionsDisplay 类。它实现了观察者接口和实现接口,包含一个主题对象,在初始化实例的时候需要传入主题对象进行注册,实现两个接口中的方法。

public class CurrentConditionsDisplay implements Observer, DisplayElement {
    private float temperature;
    private float humidity;
    private Subject weatherData;

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

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

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

最后是测试类。

public class WeatherStation {
    public static void main(String[] args) {
        WeatherData weatherData = new WeatherData();

        CurrentConditionsDisplay currentConditionsDisplay = new CurrentConditionsDisplay(weatherData);
        weatherData.setMeasurements(80,65, 30.4f);
    }
}

创建一个主题对象,创建一个布告板对象,通过主题对象的进行更新数据,会在控制台打印出布告板要展示的数据。

这是一个明显的一对多结构,在一的一方存有集合,多的一方存有一的对象。当需要进行数据更新的时候,调用方法进行一连串的行动,这样将二者进行松耦合以后,想要添加新的布告板只需要建立新类实现 Observer 接口,然后注册到主题对象即可。

明天继续下个设计模式。

上一篇:USound获3000万美元融资,为全球品牌扩大产品制造规模


下一篇:观察者模式