Java设计模式--观察者模式

观察者模式

参考《Head First设计模式》中的观察者模式完成。

  • 气象监测应用需求
  • 观察者模式介绍
  • 手写观察者模式
  • Java内置的观察者模式

气象监测应用需求

根据气象站实时输出的湿度、温度和气压值制作三块布告板。第一块布告板实时显示当前的温度、湿度和气压;第二块布告板显示当日的平均温度、最低温度以及最高温度;第三块布告板根据天气显示预报信息。

气象站提供了WeatherData类来获得实时测量的温度、湿度和气压值。
Java设计模式--观察者模式_第1张图片
气象站提供的接口如上如所示,三个getter方法用于获取温度、湿度和气压;每当气象测量值变化,就会调用measurementsChanged()方法,实现measurementsChanged()就是我们的工作。
分析:
考虑到需求中布告板显示的内容可能会发生变化,为了方便日后修改程序,希望每次只需要添加一块新的布告板,而其他程序不需要变化,努力做到交互对象之间松耦合。这个需求中,天气测量值一旦变化,三个布告板要及时获得改变并展示,完全符合观察者模式。

观察者模式介绍

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

观察者模式中有主题和观察者两个对象,以报纸的订阅为例简述观察者模式。

  1. 报社的任务就是出版报纸;
  2. 向某家报社订阅报纸,只要有新报纸出版,报社就会给你送来。只要你订阅了该报纸,就会一直收到新报纸;
  3. 当你不想看该报纸时,取消订阅,他们就不会再送报纸来;
  4. 只要报社还在运营,就一直有人向他们订阅或取消订阅报纸。

    把订阅报纸类比为观察者模式,出版者就是“主题”,订阅者就是“观察者”。在该需求中,气象站就是一个“主题”,而每块布告板就是一个“观察者”。每个布告板(观察者)向气象站(主题)注册,就可以在测量值变化时获得消息;取消某布告板类比为“观察者”向“主题”注销;新增一块布告板类比为新增一个气象“主题”的“观察者”。

手写观察者模式

面向对象的设计原则:针对接口编程,不针对实现编程。

Java设计模式--观察者模式_第2张图片
根据上述原则,把主题和观察者分别抽象为Subject和Observer接口。并添加一个DisplayElement接口用于展示。
Observer接口中只有一个update()方法,用于当主题变化时执行。
Subject接口中有使得观察者订阅的方法registerObserver();当观察者不想接收信息时的取消订阅方法removeObserver();以及当主题数据发生改变时通知订阅该主题的所有观察者的notifyObserver()方法。实际的主题,例如本需求中的WeatherData需要实现该接口,内部维护一个观察者数组。registerObserver()方法中只需要把观察者加入自身的观察者数组;removeObserver()方法中把要取消订阅的观察者移除;notifyObserver()则是遍历当前的观察者数组,依次调用每个观察者的update()即可。

Subject接口

/**
 * Created by Janet on 2017/11/6.
 * 观察者模式中主题的接口
 */
public interface Subject {
    public void registerObserver(Observer o);//观察者o订阅主题
    public void removeObserver(Observer o);//观察者o取消订阅主题
    public void notifyObserver();//主题通知观察者
}

Observer接口

/**
 * Created by Janet on 2017/11/6.
 * 观察者模式中观察者的接口
 */
public interface Observer {
    public void update(float temp,float humidity,float pressure);//主题传来通知
}

用于展示的接口

/**
 * Created by Janet on 2017/11/9.
 * 展示结果的接口
 */
public interface DisplayElement {
    public void display();
}

WeatherData主题

import java.util.ArrayList;

/**
 * Created by Janet on 2017/11/6.
 * 气象预报的主题
 */
public class WeatherData implements Subject {

    private ArrayList observers;//观察者列表
    private float temperature;//温度
    private float humidity;//湿度
    private float pressure;//气压

    //每次设置温湿度和气压,主题都会发生改变
    public void setTemperature(float temperature,float humidity,float pressure){
        this.temperature = temperature;
        this.humidity = humidity;
        this.pressure = pressure;
        measurementsChanged();
    }

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

    //主题发生改变时执行的方法
    public void measurementsChanged(){
        notifyObserver();
    }

    //观察者o订阅主题
    @Override
    public void registerObserver(Observer o) {
        observers.add(o);
    }

    //观察者o取消订阅主题
    @Override
    public void removeObserver(Observer o) {
        int index = observers.indexOf(o);
        if( index >= 0 ){
            observers.remove(o);
        }
    }

    //主题通知所有订阅者
    @Override
    public void notifyObserver() {
        for(int i = 0;i

观察者1–展示温度湿度气压的布告板

/**
 * Created by Janet on 2017/11/6.
 * 第一块展示温湿度的布告板
 */
public class CurrentConditionDisplay implements Observer,DisplayElement {

    private Subject weatherData;
    private float temperature;
    private float humidity;

    //一创建就向主题注册
    public CurrentConditionDisplay(Subject weatherData){
        this.weatherData = weatherData;
        weatherData.registerObserver(this);
    }

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

    //主题变化时执行的函数
    @Override
    public void update(float temp, float humidity, float pressure) {
        this.temperature = temp;
        this.humidity = humidity;
        display();
    }
}

观察者2–展示温度最大最小及平均值的布告板

/**
 * Created by Janet on 2017/11/6.
 * 第二块展示最小,平均,最大温度的布告板
 */
public class StatisticsDisplay implements Observer,DisplayElement {

    private float minTemperature = Float.MAX_VALUE;//最小温度
    private float avgTemperature;//平均温度
    private float maxTemperature = Float.MIN_VALUE;//最大温度
    private int num = 0;//用于计算平均温度
    private Subject weatherData;

    //创建观察者时要向主题注册
    public StatisticsDisplay(Subject weatherData){
        this.weatherData = weatherData;
        weatherData.registerObserver(this);
    }

    @Override
    public void display() {
        System.out.println("Avg/Max/Min temperature = "+avgTemperature+"/"+maxTemperature+"/"+minTemperature);
    }

    //主题变化时执行的函数
    @Override
    public void update(float temp, float humidity, float pressure) {

        if( minTemperature > temp ){
            minTemperature = temp;
        }
        if( maxTemperature < temp ){
            maxTemperature = temp;
        }
        avgTemperature = (avgTemperature * num + temp)/(num+1);
        num++;
        display();
    }
}

测试函数

/**
 * Created by Janet on 2017/11/6.
 */
public class WeatherStation {
    public static void main(String[] args){
        WeatherData weatherData = new WeatherData();
        CurrentConditionDisplay o1 = new CurrentConditionDisplay(weatherData);//第一块布告板
        StatisticsDisplay o2 = new StatisticsDisplay(weatherData);
        weatherData.setTemperature(19,20,1000);
        weatherData.setTemperature(13,15,1500);
        weatherData.setTemperature(0,22,1000);
    }
}

执行结果如下:
Java设计模式--观察者模式_第3张图片

Java内置的观察者模式

java.util包中的Observable是主题的超类(注意,java内置的主题是类不是接口),Observer是观察者的接口。
Java设计模式--观察者模式_第4张图片
实际主题需要继承超类Observeable,已经写好了观察者订阅,取消订阅以及通知的方法。其中setChanged()方法用于当主题数据修改时,用来标记状态已经改变。WeatherData内部的notifyObservers()方法会首先判断标志位是否更改,再通知各观察者。
观察者获取主题变化的数据实际上有“推”和“拉”两种方式。“推”表示主题数据发生变化时,主动把变化的数据推送给订阅的观察者们;“拉”表示当观察者需要时主动向主题索取数据。上文中我们只是自己实现了主题“推数据”的方法。Java内置的观察者模式支持“推”和“拉”两种获取数据的模式。

利用Java内置实现WeatherData

注意,使用Java内置主题时,notifyObservers()有两种重载方法:
notifyObservers()和notifyObservers(Object arg);notifyObservers(Object arg)可以传送指定数据给观察者。notifyObservers()用于拉数据的模式,notifyObservers(Object arg)用于推的模式。

import java.util.Observable;

/**
 * Created by Janet on 2017/11/9.
 * 使用java内置类实现主题
 */
public class WeatherData extends Observable{//继承Observable,内部实现了主题的创建观察者列表等方法
    private float temperature;
    private float humidity;
    private float pressure;

    public WeatherData(){}//此处无需自行创建观察者列表,超类已经创建

    public void setMeasurements(float temperature,float humidity,float pressure){
        this.pressure = pressure;
        this.humidity = humidity;
        this.temperature = temperature;
        measurementsChanged();//数据改变后调用该方法
    }

    private void measurementsChanged() {
        setChanged();//设置改变标志位
        notifyObservers();//没有参数传入,说明是观察者向主题索取数据
    }

    public float getTemperature(){
        return temperature;
    }
    public float getHumidity(){
        return humidity;
    }
    public float getPressure(){
        return pressure;
    }

}

利用Java内置实现观察者

注意,Java内置的观察者获取主题动态有“推”和“拉”两种模式。推即为主题

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

/**
 * Created by Janet on 2017/11/9.
 * 使用java内置类实现观察者
 */
public class CurrentConditionsDisplay implements Observer,DisplayElement {
    private Observable observable;//观察者内部记录主题
    private float temperature;
    private float humidity;

    //构造函数中把观察者加入到主题中
    public CurrentConditionsDisplay(Observable observable){
        this.observable = observable;
        observable.addObserver(this);//主题把观察者加入到列表
    }

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

    //不同的观察者索取的数据由update方法展示
    @Override
    public void update(Observable o, Object arg) {
        if( o instanceof WeatherData ){
            WeatherData weatherData = (WeatherData) o;
            this.temperature = weatherData.getTemperature();
            this.humidity = weatherData.getHumidity();
            display();
        }
    }
}
import java.util.Observable;
import java.util.Observer;

/**
 * Created by Janet on 2017/11/9.
 */
public class StatisticsDisplay implements Observer,DisplayElement {
    private Observable observable;
    private float minTemperature = Float.MAX_VALUE;//最小温度
    private float avgTemperature;//平均温度
    private float maxTemperature = Float.MIN_VALUE;//最大温度
    private int num = 0;//用于计算平均温度

    public StatisticsDisplay(Observable observable){
        this.observable = observable;
        observable.addObserver(this);
    }
    @Override
    public void display() {
        System.out.println("Avg/Max/Min temperature = "+avgTemperature+"/"+maxTemperature+"/"+minTemperature);
    }

    @Override
    public void update(Observable o, Object arg) {
        if( o instanceof WeatherData ){
            WeatherData weatherData = (WeatherData) o;
            float temp = weatherData.getTemperature();
            if( minTemperature > temp ){
                minTemperature = temp;
            }
            if( maxTemperature < temp ){
                maxTemperature = temp;
            }
            avgTemperature = (avgTemperature * num + temp)/(num+1);
            num++;
            display();
        }
    }
}
import java.util.Observable;
import java.util.Observer;

/**
 * Created by Janet on 2017/11/9.
 */
public class ForecastDisplay implements Observer,DisplayElement {
    private Observable observable;//主题
    private float currentPressure = 29.92f;
    private float lastPressure;

    public ForecastDisplay(Observable observable){
        this.observable = observable;
        observable.addObserver(this);
    }

    @Override
    public void display() {
        if( this.currentPressure < this.lastPressure ){
            System.out.println("气压变小");
        }else if( this.currentPressure > this.lastPressure ){
            System.out.println("气压变大");
        }else{
            System.out.println("气压不变");
        }

    }

    @Override
    public void update(Observable o, Object arg) {
        if( o instanceof WeatherData ){
            WeatherData weatherData = (WeatherData) o;
            this.lastPressure = currentPressure;
            this.currentPressure = weatherData.getPressure();
            display();
        }
    }
}

测试函数

/**
 * Created by Janet on 2017/11/9.
 */
public class WeatherStation {
    public static void main(String[] args){
        WeatherData weatherData = new WeatherData();
        CurrentConditionsDisplay currentConditionsDisplay = new CurrentConditionsDisplay(weatherData);
        ForecastDisplay forecastDisplay = new ForecastDisplay(weatherData);
        StatisticsDisplay statisticsDisplay = new StatisticsDisplay(weatherData);
        weatherData.setMeasurements(19,20,1000);
        weatherData.setMeasurements(13,15,1500);
        weatherData.setMeasurements(0,22,1000);
    }
}

运行结果如下:
Java设计模式--观察者模式_第5张图片

Java内置和手写观察者模式的区别

  1. Java内置的主题采用类的形式,扩展性不如接口;
  2. Java内置观察者创建的顺序不等同于主题改变时通知的顺序,在上例子中可见,而自写的主题内部维护观察者数组采用有序的ArrayList,可以保证顺序。

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