1

一、了解观察者模式

1.1 什么是观察者模式

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

典型的问题比如报社,只要你是他们的订户,他们每次有新报纸出版时,就会向你这送来,当你不想要看报纸时,取消订阅,他们就不会再给你送报纸。

1.2 观察者模式组成结构

  • 抽象主题 (Subject):抽象主题角色把所有观察者对象保存在一个集合里,每个主题都可以有任意数量的观察者,抽象主题提供一个接口,可以增加和删除观察者对象。
  • 具体主题 (ConcreteSubject):该角色将有关状态存入具体观察者对象,在具体主题的内部状态发生改变时,给所有注册过的观察者发送通知。
  • 抽象观察者 (Observer):是观察者的抽象类,它定义了一个更新接口,使得在得到主题更改通知时更新自己。
  • 具体观察者 (ConcrereObserver):实现抽象观察者定义的更新接口,以便在得到主题更改通知时更新自身的状态。

1.3 观察者模式 UML 图解

这里写图片描述

二、观察者模式具体应用

2.1 问题描述

气象观测站系统:该系统中包含三部分,分别是气象站 (获取实际气象数据的物理装置)、WeatherData 对象 (追踪气象站的数据,并更新布告板) 和布告板 (显示天气状况给用户看,布告板共有两个,分别显示当前的温度以及对天气进行预告)。

2.2 问题分析

这里写图片描述

我们想要使用观察者模式去解决这个问题,首先要分析出什么是主题,什么是观察者,问题的关键是找出一对多依赖关系。这里 WeatherData 类正如所说的“一”,而“多”是用于显示天气情况的布告板。

WeatherData 是有状态的对象,它包括了温度、湿度和气压,而这些值都会变化,当这些值改变时,必须通知布告板,好让它们显示最新的数据。所以把 WeatherData 类作为主题,布告板作为观察者。

2.3 问题分析设计图

这里写图片描述

2.4 代码实现

PS:代码模块较多,建议将这些代码拷下来运行一遍。

抽象主题接口 Subject

package com.jas.observer;

public interface Subject {

    /**
     *  注册观察者
     *  
     * @param observer 观察者对象
     */
    void registObserver(Observer observer);

    /**
     *  移除观察者
     *
     * @param observer 观察者对象
     */
    void removeObserver(Observer observer);

    /**
     * 当主题状态改变时,这个方法会被调用,通知所有的观察者
     */
    void notifyObservers();
}

抽象观察者接口 Observer

package com.jas.observer;

public interface Observer {

    /**
     * 当气象观测值改变时,主题会把这些状态值作为参数,传送给观察者
     * 
     * @param temp 温度
     * @param humidity  湿度
     * @param pressure  压力
     */
    void update(float temp, float humidity, float pressure);
}

布告信息接口 DisplayElement

package com.jas.observer;

public interface DisplayElement {
    void display();
}

具体主题类 WeatherData

package com.jas.observer;

import java.util.ArrayList;
import java.util.List;

public class WeatherData implements Subject {
    private float temperature;
    private float humidity;
    private float pressure;
    private List<Observer> list = new ArrayList();    //使用集合保存所有的观察者对象
    
    
    @Override
    public void registObserver(Observer observer) {
        list.add(observer);
    }

    @Override
    public void removeObserver(Observer observer) {
        int i = list.indexOf(observer);
        if(i >= 0 && i < list.size()){
            list.remove(i);
        }
    }

    @Override
    public void notifyObservers() {
        for (int i = 0; i < list.size(); i++) {    //遍历集合中所有观察者对象
            Observer observer = list.get(i);
            observer.update(temperature,humidity,pressure);    //调用观察者的 update() 方法
        }
    }

    /**
     * 当气象站的数据得到更新后,通知观察者,调用 notifyObservers() 方法
     */
    public void measurementsChanged(){
        notifyObservers();
    }

    /**
     * 当气象站数据改变后,设置新的数据值,并调用 measurementsChanged() 方法
     * 
     * @param temperature  温度
     * @param humidity  湿度
     * @param pressure  气压
     */
    public void setMeasurements(float temperature, float humidity, float pressure){
        this.temperature = temperature;
        this.humidity = humidity;
        this.pressure = pressure;
        measurementsChanged();
    }
}

具体观察者对象,当前天气信息类 CurrentConditionsDisplay

package com.jas.observer;

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

    /**
     * 通过构造函数将当前观察者注册给具体主题对象
     * 
     * @param weatherData 主题对象
     */
    public CurrentConditionsDisplay(Subject weatherData){
        this.weatherData = weatherData;
        weatherData.registObserver(this);
    }
    
    /**
     * 布告板信息展示
     */
    @Override
    public void display() {
        System.out.println("Current conditions list : " + "温度 = " + 
                temperature + ", 湿度 = " + humidity + ", 气压 = " + pressure);
    }

    /**
     * 更新信息
     * 
     * @param temp 温度
     * @param humidity  湿度
     * @param pressure  压力
     */
    @Override
    public void update(float temp, float humidity, float pressure) {
        this.temperature = temp;
        this.humidity = humidity;
        this.pressure = pressure;
        display();
    }
}

具体观察者对象,预测天气信息类 ForecastDisplay(简单将数据减一)

package com.jas.observer;

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

    /**
     * 通过构造函数将当前观察者注册给主题对象
     *
     * @param weatherData 主题对象
     */
    public ForecastDisplay(WeatherData weatherData){
        this.weatherData = weatherData;
        weatherData.registObserver(this);
    }
    
    @Override
    public void display() {
        System.out.println("Forecast conditions list : " + "温度 = " +
                (temperature - 1.0) + ", 湿度 = " + (humidity - 1.0) + ", 气压 = " + (pressure - 1.0));
    }

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

气象站类 WeatherStation

package com.jas.observer;

public class WeatherStation {
    public static void main(String[] args) {
        
        WeatherData weatherData = new WeatherData();
        
        CurrentConditionsDisplay currentDisplay = new CurrentConditionsDisplay(weatherData);
        ForecastDisplay forecastDisplay = new ForecastDisplay(weatherData);
        
        //当具体主题对象数据发生变化,所有依赖者 (观察者) 都会实现自动数据更新
        weatherData.setMeasurements(18,65,30);
    }
}

    /**
     * 输出
     * Current conditions list : 温度 = 18.0, 湿度 = 65.0, 气压 = 30.0
     * Forecast conditions list : 温度 = 17.0, 湿度 = 64.0, 气压 = 29.0
     */

2.5 自定义观察者模式总结

观察者模式可以轻松实现松耦合,因为主题并不需要知道观察者的具体类是谁,做了些什么,并且我们可以在任何时候新增观察者。由于一个主题可能对应多个观察者,所以当某一个观察者出现问题时,可能导致其他的观察者也不能正常工作。因此在一定程度上,存在着效率问题。

三、Java 内置观察者模式

3.1 了解 Java 内置观察者模式

java.util 包内包含最基本的Observable 类Observer 接口,这和上面的 Subject 接口与 Observer 接口很类似。Observable 类与 Observer 接口使用起来更方便,因为许多的功能已经提供了。

3.2 Java 内置观察者模式如何运作

(1)如何把对象定义为观察者?

实现观察者 (Observer) 接口,调用任何 Observable 对象的 addObserve() 方法。当不想要当观察者时,调用 deleteObserve() 方法。

(2)可观察者如何发送通知?

  1. 先调用 setChanged() 方法,标记状态已经被改变的事实。
  2. 调用 notifyObservers()notifyObservers(Object arg) 方法。

(3)观察者如何接收通知?

同以前一样,观察者实现了 update(Observable o, Object arg) 方法,只是方法签名不太一样。

3.3 重写气象观测站系统

主题类 WeatherData

package com.jas.jdk.observer;

import java.util.Observable;

public class WeatherData extends Observable {
    private float temperature;
    private float humidity;
    private float pressure;
    
    public void measurementsChanged(){
        //在通知所有观察者之前,先调用 setChanged() 方法,用来表示状态已经改变
        setChanged();
        notifyObservers();
    }
    
    public void setMeasurements(float temperature, float humidity, float pressure){
        this.temperature = temperature;
        this.humidity = humidity;
        this.pressure = pressure;
        measurementsChanged();
    }
    
    public float getTemperature(){
        return temperature;
    }
    
    public float getHumidity(){
        return humidity;
    }

    public float getPressure() {
        return pressure;
    }
}

具体观察者对象,当前天气信息类 CurrentConditionsDisplay

package com.jas.jdk.observer;

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

public class CurrentConditionsDisplay implements Observer, DisplayElement {
    private float temperature;
    private float humidity;
    private float pressure;
    private Observable observable;

    /**
     * 通过构造函数,将当前对象记录为观察者
     * 
     * @param observable 主题对象
     */
    public CurrentConditionsDisplay(Observable observable){
        this.observable = observable;
        observable.addObserver(this);
    }
    
    @Override
    public void display() {
        System.out.println("Current conditions list : " + "温度 = " +
                temperature + ", 湿度 = " + humidity + ", 气压 = " + pressure);
    }

    @Override
    public void update(Observable o, Object arg) {
        if(o instanceof WeatherData){
            WeatherData weatherData = (WeatherData) o;
            this.temperature = weatherData.getTemperature();
            this.humidity = weatherData.getHumidity();
            this.pressure = weatherData.getPressure();
            display();
        }
    }
}

气象站类 WeatherStation(同上)

package com.jas.jdk.observer;


public class WeatherStation {
    public static void main(String[] args) {
        
        WeatherData weatherData = new WeatherData();
        
        CurrentConditionsDisplay currentDisplay = new CurrentConditionsDisplay(weatherData);
        //ForecastDisplay forecastDisplay = new ForecastDisplay(weatherData);
        
        weatherData.setMeasurements(80,65,30.4f);
    }
}

     /**
     * 输出
     * Current conditions list : 温度 = 80.0, 湿度 = 65.0, 气压 = 30.4
     */

3.4 Java 内置观察者模式总结

Java 内置的观察者模式允许观察者有选择的获取数据,而不是主题对象强制将更新数据全部推送个每个观察者。

Observable 是一个类,并不是一个接口,这意味着你继承它的同时,不能再继承其他的类。在 Observable 类中 setChanged() 方法被保护了起来 (protected),除非你继承该类,否则你无法创建 Observable 实例组合到你自己的对象中来。所以它违反了一个原则:“多用组合,少用继承”。

还有一点需要要注意的是:内置的观察者模式,观察者被通知的顺序并不是唯一的 (上面只定义了一个观察者),有时候并不能达到我们一开始的目的,你可以定义多个观察者验证一下。

根据具体的需求,如果 Java 内置的观察者模式 API 不能满设计,那么我们可以像刚开始那样自己实现一套观察者模式。

参考资料

《Head First 设计模式》


留兰香
123 声望5 粉丝