HeadFirst设计模式(二) - 观察者模式

有趣的事情发生时,可千万别错过了!

    观察者模式是JDK中使用最多的模式之一,非常有用。我们也会一并介绍一对多关系,以及松耦合。有个观察者,你将会消息灵通。

工作合约

    某软件公司接到一个工作合约,内容如下:

    恭喜贵公司获选为敝公司(Weather-O-Rama气象站)建立下一代Internate气象站!

    该气象站必须建立在我们专利申请中的WeatherData对象上,由WeatherData负责监测目前的天气情况(温度、湿度和气压等)。我们希望贵公司建立一个应用,有三种布告板,分别显示目前的情况,气象统计及简单的预报。当WeatherData对象获得最新的监测数据时,三种布告板实时更新。

    而且,这是一个可以拓展的气象站,Weather-O-Rama气象站希望发布一组API,好让其他开发人员可以写出自己的气象布告板,并插入次应用中。我们希望贵公司提供这样的API。

气象监测站应用的概况

    此系统中的三个部分是气象站(获取实际气象数据的物理装置)、WeatherData对象(最终来自气象站的数据)和布告板(显示目前天气状况给用户看)。如图:


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

    我们的工作就是建立一个应用,利用WeatherData对象获得数据,并更新三个布告板。    

    瞧一瞧客户给提供的WeatherData类吧。

    我们的工作是实现measurementsChanged()方法,好让它更新目前的状况、气象统计和天气预报的显示布告板。

我们目前知道什么?

    Weather-O-Rama气象站的要求说明并不是很清楚,我们必须搞懂该做些什么,那么,我们目前知道些什么呢?

  • WeatherData类具有getter方法,可以取得三个测量值:温度、湿度与气压。

  • 当新的测量数据准备好时,measurementsChanged()方法就会被调用(我们不在乎次方法是如何被调用的,我们只在乎它被调用了,也许被某个其他的程序调用的,比如硬件采集器,这是Weather-O-Rama气象站提供的WeatherData类已经实现的功能)。

  • 我们需要实现三个使用天气数据的布告板,一旦WeatherData有新的测量数据就马上更新。

  • 次系统必须可拓展,让其他开发人员自定义布告板。

先看一个错误的示范

    如果不进行思考,直接进入开发阶段,只为了完成工作而工作,那么这是第一个可能的实现。我们依照Weather-O-Rama气象站开发人员的暗示,在客户提供的类的measurementsChanged()方法中添加我们的代码:

package cn.net.bysoft.observer;

/**
 * 这是一个没有经过任何思考而写的代码。
 * */
public class WeatherData {

    // 该方法在硬件有最新监测数据时被调用。
    public void measurementsChanged() {
        //    该类中的代码又我们公司实现。
        
        // 调用三个getter方法获得最新的监测数据。
        float temp = getTemperature();
        float humidity = getHumidity();
        float pressure = getPressure();

        // 更新三个我们公司自己开发的布告板类。
        currentConditionsDispaly.update(temp, humidity, pressure);
        statisticsDisplay.update(temp, humidity, pressure);
        forecastDispaly.update(temp, humidity, pressure);
    }

    public float getTemperature() {
        return temperature;
    }

    public float getHumidity() {
        return humidity;
    }

    public float getPressure() {
        return pressure;
    }

    // 这三个setter方法可能会在硬件监测到最新的气象数据时被赋值。
    // 然后硬件会调用measurementsChanged()方法。
    public void setTemperature(float temperature) {
        this.temperature = temperature;
    }

    public void setHumidity(float humidity) {
        this.humidity = humidity;
    }

    public void setPressure(float pressure) {
        this.pressure = pressure;
    }

    private float temperature;
    private float humidity;
    private float pressure;
}

    在我们的第一个实现中,存在一下的缺点:

  • 我们是针对具体实现编程,而非针对接口;

  • 对于每个新的布告版,我们都得修改代码;

  • 我们无法在运行时动态地添加或删除布告板;

  • 我们尚未封装改变的部分;

我们的实现有什么不对?

    针对具体实现编程,会导致我们以后在添加或删除布告板时必须修改程序。而且,布告板的update()方法看起来是一个统一的接口,布告板的方法名称都是update(),参数都是温度、湿度和气压。

    现在就来看看观察者模式,然后再回来看看如何将此模式应用到气象观测站。

    观察者就好像订阅报纸或者杂志。

  1. 报社的业务就是出版报纸。

  2. 向某家报社订阅报纸,只要他们有新报纸出版,就会给你送来。只要你是他们的订户,你就会一直收到新报纸。

  3. 当你不想再看报纸的时候,取消订阅,他们就不会再送新报纸来。

  4. 只要报社还在运营,就会一直有人向他们订阅报纸或取消订阅。

    订阅者+出版者=观察者模式,只是名称不太一样。

    出版者改为主题(Subject),订阅者改为观察者(Observer),一个主题对应多个观察者,是一个一对多的关系。来看一下观察者的类图:

HeadFirst设计模式(二) - 观察者模式_第1张图片

    这里有一个设计原则:

设计原则:

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

松耦合的威力

    当两个对象之间松耦合,他们依然可以交互,但是不太清楚彼此的细节。

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

    关于观察者的一切,主题值知道观察者实现了某个接口。主题不需要知道观察者的具体类是谁,做了些什么或其他任何细节。

    任何时候我们都可以添加新的观察者。因为主题唯一依赖的东西是一个实现Observer接口的对象列表,所以我们可以随时添加观察者。事实上,在运行时我们可以用新的观察者取代现有的观察者,主题不会受到任何影响。同样的,也可以在任何时候删除某些观察者。

    有新类型出现的观察者出现时,主题的代码不需要修改。我们可以独立地复用主题或观察者。如果我们在其他任何地方需要使用主题或观察者,可以轻易地复用,因为二者并非紧耦合,而是松耦合。

    松耦合的设计之所以能让我们建立有弹性的OO系统,能够应对变化,是因为对象之间的互相依赖降到了最低。

设计与实现气象站

    了解了这么多,开始设计与实现气象站吧。

    设计类图如上图所示,让我们从建立接口开始吧!

package cn.net.bysoft.observer;

/**
 * 布告板显示信息接口。
 * */
public interface DisplayElement {
    /**
     * 当布告板需要显示时,调用次方法。
     * */
    public void display();
}
package cn.net.bysoft.observer;

/**
 * 观察者接口。
 * */
public interface Observer {
    /**
     * 当气象监测数据改变时,主题会把这些状态值当作方法的参数,传送给观察者。
     * */
    public void update(float temp, float humidity, float perssure);
}
package cn.net.bysoft.observer;

/**
 * 主题接口。
 * */
public interface Subject {
    /**
     * registerObserver和removeObserver方法都需要一个观察者作为变量,该观察者是用来注册或被删除的。
     * */
    public void registerObserver(Observer ob);
    
    public void removeObserver(Observer ob);
    
    /**
     * 当主题的状态改变时,这个方法会被调用,已通知所有的观察者。
     * */
    public void notifyObservers();
}

    接下来,我们要用观察者模式实现WeatherData类了。

package cn.net.bysoft.observer;

import java.util.ArrayList;

public class WeatherData implements Subject {
    public WeatherData() {
        observers = new ArrayList<Observer>();
    }
    
    /**
     * 注册观察者。
     * */
    public void registerObserver(Observer ob) {
        observers.add(ob);
    }

    /**
     * 移除观察者。
     * */
    public void removeObserver(Observer ob) {
        int index = observers.indexOf(ob);
        if (index >= 0)
            observers.remove(index);
    }

    /**
     * 当有最新的气象监测数据时被调用。↓下面的函数调用。
     * */
    public void notifyObservers() {
        for (Observer ob : observers) {
            ob.update(this.temperature, this.himidity, this.pressure);
        }
    }
    
    /**
     * 气象站硬件监测到新数据会调用该方法。↓下面的函数调用。
     * */
    public void measurementsChanged() {
        notifyObservers();
    }
    
    /**
     * 因为没有气象站,所以模拟一个硬件,获得气象数据调用measurementsChanged()方法。
     * 在main()函数中使用。
     * */
    public void setMeasurements(float temperature, float himidity, float pressure) {
        this.temperature = temperature;
        this.himidity = himidity;
        this.pressure = pressure;
        measurementsChanged();
    }

    public ArrayList<Observer> getObservers() {
        return observers;
    }

    public void setObservers(ArrayList<Observer> observers) {
        this.observers = observers;
    }

    public float getTemperature() {
        return temperature;
    }

    public void setTemperature(float temperature) {
        this.temperature = temperature;
    }

    public float getHimidity() {
        return himidity;
    }

    public void setHimidity(float himidity) {
        this.himidity = himidity;
    }

    public float getPressure() {
        return pressure;
    }

    public void setPressure(float pressure) {
        this.pressure = pressure;
    }

    private ArrayList<Observer> observers;
    private float temperature;
    private float himidity;
    private float pressure;
}

最后,编写布告板吧!

    我们已经把WeatherData类写出来了,现在轮到布告板了。Weather-O-Rama气象站订购了三个布告板,首先来实现其中一个布告板。

package cn.net.bysoft.observer;

public class CurrentConditionsDisplay implements Observer, DisplayElement {

    public CurrentConditionsDisplay() {}
    public CurrentConditionsDisplay(Subject weatherData) {
        //    实例化布告板的时候,传入一个主题,将自己注册到主题中。
        this.weatherData = weatherData;
        weatherData.registerObserver(this);
    }
    
    public void display() {
        System.out.println("当前conditions:" + temperature + "F degrees and "
                + himidity + "% humidity");
    }

    public void update(float temp, float humidity, float perssure) {
        this.temperature = temp;
        this.himidity = humidity;
        // 更新数据后刷新布告板。
        display();
    }

    private float temperature;
    private float himidity;
    //    要观察的主题。
    private Subject weatherData;
}

    布告板已经实现完毕,编写一个main()方法测试一下观察者模式吧!

package cn.net.bysoft.observer;

public class Main {

    public static void main(String[] args) {
        // 首先,创建一个主题。
        // 这里应该使用Subject weatherData = new WeatherData();来创建主题。
        // 但是我们在WeatherData中添加了一个模拟硬件发送气象数据的函数,Subject中并没有这个函数。
        // 所以使用了WeatherData来创建主题。
        WeatherData weatherData = new WeatherData();
        // 建立一个观察者(也就是布告板),把主题传给观察者,在观察者的构造函数中订阅主题。
        Observer ccDisplay = new CurrentConditionsDisplay(weatherData);

        // 模拟一个气象变化的情况。
        weatherData.setMeasurements(80, 65, 30.4f);
        System.out.println("\n====================\n");

        // 又变化了!
        weatherData.setMeasurements(80, 65, 30.4f);

        /**
         * output: 
         * 当前conditions:80.0F degrees and 65.0% humidity
         * 
         * ====================
         * 
         * 当前conditions:80.0F degrees and 65.0% humidity
         * 
         * */
    }

}

    另外两个布告板如上代码,另外,Java中提供了内置的观察者模式接口,它们是:

  • java.util.Observable

  • java.util.Observer

    想要进一步了解可以网上查找资料,或者和我一样购买HeadFirst设计模式这本书,这本书真的很不错。

    以上就是观察者模式。

你可能感兴趣的:(HeadFirst设计模式(二) - 观察者模式)