观察者模式

Mr.LR2022年5月28日
大约 6 分钟

观察者模式

天气预报需求

具体要求如下:

  1. 气象站可以将每天测量到的温度,湿度,气压等等以公告的形式发布出去(比如发布到自己的网站或第三方)
  2. 需要设计开放型 API,便于其他第三方也能接入气象站获取数据
  3. 提供温度、气压和湿度的接口
  4. 测量数据更新时,要能实时的通知给第三方

传统方案解决天气预报需求

WeatherData类

通过对气象站项目的分析,我们可以初步设计出一个WeatherData

image-20220907205154530

  1. 通过getXxx方法,可以让第三方接入,并得到相关信息
  2. 当数据有更新时,气象站通过调用dataChange()去更新数据,当第三方再次获取时,就能得到最新数据,当然也可以推送

image-20220907205216707

CurrentConditions(当前的天气情况)可以理解成是我们气象局的网站

核心代码

//当前的天气情况:可以理解成是气象局的网站
public class CurrentConditions {
    private Float temperature;
    private Float pressure;
    private Float humidity;

    /**
     * 更新天气情况,通过推送的方式,由 WeatherData 调用
     *
     * @param temperature
     * @param pressure
     * @param humidity
     */
    public void update(Float temperature, Float pressure, Float humidity) {
        // 更新最新天气数据
        this.temperature = temperature;
        this.pressure = pressure;
        this.humidity = humidity;
        // 展示最新天气数据
        display();
    }

    /**
     * 公告板展示天气情况
     */
    public void display() {
        System.out.println("============最新天气============");
        System.out.println("*** 当前温度:" + this.temperature + " ℃ ***");
        System.out.println("*** 当前气压:" + this.pressure + " kPa ***");
        System.out.println("*** 当前湿度:" + this.humidity + " %RH ***");
    }
}
/**
 * 核心类
 * 1、包含最新的天气信息情况
 * 2、含有 CurrentConditions 对象
 * 3、当数据更新时,主动调用 CurrentConditions 的 update() 方法
 */
public class WeatherData {
    private Float temperature;
    private Float pressure;
    private Float humidity;
    private CurrentConditions conditions;

    /**
     * 传入 CurrentConditions 对象
     *
     * @param conditions
     */
    public WeatherData(CurrentConditions conditions) {
        this.conditions = conditions;
    }

    public Float getTemperature() {
        return temperature;
    }

    public Float getPressure() {
        return pressure;
    }

    public Float getHumidity() {
        return humidity;
    }

    /**
     * 推送天气数据到网站
     */
    public void dataChange() {
        conditions.update(getTemperature(), getPressure(), getHumidity());
    }

    /**
     * 当天气数据发生变化时进行更新
     *
     * @param temperature
     * @param pressure
     * @param humidity
     */
    public void setData(Float temperature, Float pressure, Float humidity) {
        this.temperature = temperature;
        this.pressure = pressure;
        this.humidity = humidity;
        dataChange();
    }
}
public class Client {
    public static void main(String[] args) {
        // 创建气象网站对象
        CurrentConditions currentConditions = new CurrentConditions();
        // 创建气象数据对象,并传入气象网站对象
        WeatherData weatherData = new WeatherData(currentConditions);
        // 天气发生变化时,更新最新的气象数据
        weatherData.setData(10f, 150f, 40f);

    }
}

传统方案问题

  1. 其他第三方接入气象站获取数据的问题
  2. 无法在运行时动态的添加第三方(新浪网站)
  3. 违反OCP原则 => 观察者模式

WeatherData中增加第三方时,都需要创建对应的第三方公台板对象并加入到dataChange()方法中,既不是动态加入,也不利于维护

观察者模式原理

观察者模式类似订牛奶业务

  1. 奶站 / 气象局:Subject
  2. 用户 / 第三方网站:Observer

Subject:登记注册、移除和通知

image-20220907210058420

  1. registerObserver():注册
  2. removeObserver():移除
  3. notifyObservers():通知所有的注册的用户,根据不同需求,可以是更新数据,让用户来取,也可能是实施推送,看具体需求定

Observer:接收输入

image-20220907210119967

观察者模式:对象之间多对一依赖的一种设计方案,被依赖的对象为Subject,依赖的对象为ObserverSubject通知Observer变化,比如这里的奶站是Subject,是1的一方。用户是Observer,是多的一方

观察者模式解决天气预报需求

uml类图

image-20220907212021301

image-20220907212318453

核心代码

//观察者接口
public interface Observer {
    void update(Float temperature, Float pressure, Float humidity);
}
//观察者实现
public class CurrentConditions implements Observer {
    private Float temperature;
    private Float pressure;
    private Float humidity;

    @Override
    public void update(Float temperature, Float pressure, Float humidity) {
        // 更新最新天气数据
        this.temperature = temperature;
        this.pressure = pressure;
        this.humidity = humidity;
        // 展示最新天气数据
        display();
    }

    /**
     * 公告板展示天气情况
     */
    public void display() {
        System.out.println("============最新天气============");
        System.out.println("*** 当前温度:" + this.temperature + " ℃ ***");
        System.out.println("*** 当前气压:" + this.pressure + " kPa ***");
        System.out.println("*** 当前湿度:" + this.humidity + " %RH ***");
    }
}
//主体对象接口
public interface Subject {
    void registerObserver(Observer o);

    void removeObserver(Observer o);

    void notifyObservers();
}
//主体对象实现
public class WeatherData implements Subject {
    private Float temperature;
    private Float pressure;
    private Float humidity;
    private List<Observer> observerList;

    public WeatherData() {
        observerList = new ArrayList<>();
    }

    public Float getTemperature() {
        return temperature;
    }

    public Float getPressure() {
        return pressure;
    }

    public Float getHumidity() {
        return humidity;
    }

    /**
     * 推送天气数据到网站
     */
    public void dataChange() {
        notifyObservers();
    }

    /**
     * 当天气数据发生变化时进行更新
     *
     * @param temperature
     * @param pressure
     * @param humidity
     */
    public void setData(Float temperature, Float pressure, Float humidity) {
        this.temperature = temperature;
        this.pressure = pressure;
        this.humidity = humidity;
        dataChange();
    }

    @Override
    public void registerObserver(Observer o) {
        observerList.add(o);
    }

    @Override
    public void removeObserver(Observer o) {
        if(o!= null && observerList.contains(o)) {
            observerList.remove(o);
        }
    }

    @Override
    public void notifyObservers() {
        for (Observer observer : observerList) {
            observer.update(temperature, pressure, humidity);
        }
    }
}
public class Client {
    public static void main(String[] args) {
        // 创建气象网站对象
        CurrentConditions currentConditions = new CurrentConditions();
        // 创建气象数据对象
        WeatherData weatherData = new WeatherData();
        // 注册气象网站对象
        weatherData.registerObserver(currentConditions);
        // 天气发生变化时,更新最新的气象数据
        weatherData.setData(10f, 150f, 40f);
    }
}

观察者模式优点

  1. 观察者模式设计后,会以集合的方式来管理用户Observer,包括注册、移除和通知
  2. 这样,我们增加观察者(这里可以理解成一个新的公告板),就不需要去修改核心类WeatherData不会修改代码,遵守了ocp原则

例如,我们新增SinaWebSiteBaiDuWebSite两个三方网站,接口气象局。此时三方只需实现相应接口即可,WeatherData不需要有任何的改变

//新增的三方观察者对象——新浪网
public class SinaWebSite implements Observer {
    private Float temperature;
    private Float pressure;
    private Float humidity;

    /**
     * 更新天气情况,通过推送的方式,由 WeatherData 调用
     *
     * @param temperature
     * @param pressure
     * @param humidity
     */
    @Override
    public void update(Float temperature, Float pressure, Float humidity) {
        // 更新最新天气数据
        this.temperature = temperature;
        this.pressure = pressure;
        this.humidity = humidity;
        // 展示最新天气数据
        display();
    }

    /**
     * 公告板展示天气情况
     */
    public void display() {
        System.out.println("============新浪网-最新天气============");
        System.out.println("*** 新浪网-当前温度:" + this.temperature + " ℃ ***");
        System.out.println("*** 新浪网-当前气压:" + this.pressure + " kPa ***");
        System.out.println("*** 新浪网-当前湿度:" + this.humidity + " %RH ***");
    }
}
//新增的三方观察者对象——百度网
public class BaiDuWebSite implements Observer {
    private Float temperature;
    private Float pressure;
    private Float humidity;

    /**
     * 更新天气情况,通过推送的方式,由 WeatherData 调用
     *
     * @param temperature
     * @param pressure
     * @param humidity
     */
    @Override
    public void update(Float temperature, Float pressure, Float humidity) {
        // 更新最新天气数据
        this.temperature = temperature;
        this.pressure = pressure;
        this.humidity = humidity;
        // 展示最新天气数据
        display();
    }

    /**
     * 公告板展示天气情况
     */
    public void display() {
        System.out.println("============百度网-最新天气============");
        System.out.println("*** 百度网-当前温度:" + this.temperature + " ℃ ***");
        System.out.println("*** 百度网-当前气压:" + this.pressure + " kPa ***");
        System.out.println("*** 百度网-当前湿度:" + this.humidity + " %RH ***");
    }
}
public class Client {
    public static void main(String[] args) {
        // 创建气象网站对象
        CurrentConditions currentConditions = new CurrentConditions();
        // 创建气象数据对象
        WeatherData weatherData = new WeatherData();
        // 注册气象网站对象
        weatherData.registerObserver(currentConditions);
        weatherData.registerObserver(new SinaWebSite());
        weatherData.registerObserver(new BaiDuWebSite());
        // 天气发生变化时,更新最新的气象数据
        weatherData.setData(10f, 150f, 40f);
    }
}

JDK 源码分析

JDK 中的Observable就使用到了观察者模式

image-20220907213647296

image-20220907213731846

角色分析和职责说明

  1. Observable即充当了Subject接口及其实现类,其中包括了Observer的集合,并且包括对观察者相关的注册、移除和通知等方法:addObserver()deleteObserver()notifyObservers(),这些方法就类似于我们上面例子中的registerObserver()removeObserver()notifyObservers()
  2. Observer即观察者接口,具有update()方法

参考

上次编辑于: 2022/9/16 16:52:16
贡献者: liurui-60837