《Head First 设计模式》笔记2

观察者模式(Observer)

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

初识

我们先来了解一下报纸和杂志的订阅是怎么回事:
1. 报社的业务就是出版报纸、杂志等各种出版物。
2. 如果我想看报社的 A 报纸和 B 杂志,那么就向报社订阅 A 报纸和 B 杂志。
3. 当他们有新的 A 报纸或 B 杂志出版时,就会向你派送,只要你是他们的订户,你就会一直收到新报纸,新杂志。
4. 如果你不想看 B 杂志了,取消订阅,他们就不会再送新的 B 杂志给你了。但不会影响你订阅的 A 报纸。
5. 只要报社还在运营,就会一直有人向他们订阅或取消报纸等出版物。

在观察者模式中,出版者报社 = 主题(subject),而我们订阅者 = 观察者(observer)。

栗子

现在有一个系统,包括三部分:
- 气象站:获取实际气象数据的物理装置。
- WeatherData 类:追踪来自气象站的数据,并更新布告板(具体怎么追踪的不用管)。
- 布告板:显示目前的天气状况。

现在的项目是,利用 WeatherData 类取得气象数据,更新三个布告板:目前状况、气象统计和天气预报。

WeatherData 类:

class WeatherData {
    private float temperature; // 温度
    private float humidity; // 湿度
    private float pressure; // 气压

    public float getTemperature() {
        return this.temperature;
    }

    public float getHumidity() {
        return this.humidity;
    }

    public float getPressure() {
        return this.pressure;
    }

    /**
     * 一旦气象数据更新,就会被调用
     */
    public void measurementsChanged() {
        // 你的代码
    }
}

而我们的工作就是实现 measurementsChanged,让它来更新我们的三个布告板(不用知道该方法是如何被调用的,我们只用知道该方法被调用时,我们的布告板也被更新了)。

布告板肯定还会添加或者删除的,所以项目一定要支持扩展。

错误示范

public void measurementsChanged() {
    // 获得最近的天气数据
    float temp = getTemperature();
    float humidity = getHumidity();
    float pressure = getPressure();
    // 更新三个布告板
    currentConditionsDisplay.update(temp, humidity, pressure);
    statisticsDisplay.update(temp, humidity, pressure);
    forecastDisplay.update(temp, humidity, pressure);
}

有什么问题呢?
1. 如果有添加和删除布告板的需求,那么就必须改动这些代码,不利于项目的扩展。(想一想每次都要修改、编译、打包就觉得累)
2. 这些布告板都有一个 update 方法,所以这些布告板应该用带有 update 方法的接口或抽象类替代而不是具体实现。

满足需求

一个 WeatherData 类和多个布告板有联系,并且布告板需要 WeatherData 类来通知数据,所以这里应该使用观察者模式。

定义主题接口:

interface Subject {
    // 观察者注册
    void registerObserver(Observer o);
    // 删除观察者
    void removeObserver(Observer o);
    // 通知所有观察者
    void notifyObservers();
}

观察者接口:

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

布告板显示功能:

interface DisplayElement {
    void display();
}

然后就是把 WeatherData 类改造成 Subject:

class WeatherData implements Subject {
    private float temperature; // 温度
    private float humidity; // 湿度
    private float pressure; // 气压

    private List<Observer> observers; // 观察者们

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

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

    @Override
    public void removeObserver(Observer o) {
        observers.remove(o);
    }

    @Override
    public void notifyObservers() {
        // 通知每一个观察者更新数据
        for (Observer observer : observers) {
            observer.update(temperature, humidity, pressure);
        }
    }

    public void measurementsChanged() {
        notifyObservers();
    }

    public float getTemperature() {
        return this.temperature;
    }

    public float getHumidity() {
        return this.humidity;
    }

    public float getPressure() {
        return this.pressure;
    }

    // 模拟数据,方便测试
    public void mock(float temperature, float humidity, float pressure) {
        this.temperature = temperature;
        this.humidity = humidity;
        this.pressure = pressure;
        measurementsChanged();
    }
}

把布告板变成观察者:

class CurrentConditionsDisplay implements Observer, DisplayElement {
    private Subject weatherData; // 保存主题,方便之后取消观察
    private float temperature;
    private float humidity;

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

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

    @Override
    public void display() {
        System.out.println("目前状况:" + temperature + " 摄氏度," + humidity + "% 湿度");
    }
}

测试

public class Main {
    public static void main(String[] args) {
        WeatherData weatherData = new WeatherData();
        CurrentConditionsDisplay display1 = new CurrentConditionsDisplay(weatherData);
        System.out.println("通知前");
        display1.display();
        System.out.println("第一次通知后");
        weatherData.mock(25, 60, 30.4f);
        System.out.println("第二次通知后");
        weatherData.mock(20, 72, 41.7f);
    }
}

输出:
通知前
目前状况:0.0 摄氏度,0.0% 湿度
第一次通知后
目前状况:25.0 摄氏度,60.0% 湿度
第二次通知后
目前状况:20.0 摄氏度,72.0% 湿度

使用 Java 内置的观察者模式

Java 内置的 Observer 接口和 Observable 类和我们实现的 Subject 接口与 Observer 接口很相似。

这里就将使用这两个内置的接口和类重写上面的天气软件。

WeatherData 类继承 Observable 类:

import java.util.Observable;

class WeatherData extends Observable {
    private float temperature; // 温度
    private float humidity; // 湿度
    private float pressure; // 气压

    public void measurementsChanged() {
        // 指示状态已经改变;如果不指示的话,notifyObservers 无法发出通知
        // 详细看源码实现
        setChanged();
        notifyObservers();
    }

    // 观察者会利用这些 getter 方法取得 WeatherData 对象的状态
    public float getTemperature() {
        return this.temperature;
    }

    public float getHumidity() {
        return this.humidity;
    }

    public float getPressure() {
        return this.pressure;
    }

    // 模拟数据,方便测试
    public void mock(float temperature, float humidity, float pressure) {
        this.temperature = temperature;
        this.humidity = humidity;
        this.pressure = pressure;
        measurementsChanged();
    }
}

布告板实现 Observer 接口:

import java.util.Observer;

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

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

    @Override
    public void update(Observable observable, Object arg) {
        // 先确定接收的是来自 WeatherData 的,而不是来自其他可观察对象的
        if (observable instanceof WeatherData) {
            WeatherData weatherData = (WeatherData) observable;
            this.temperature = weatherData.getTemperature();
            this.humidity = weatherData.getHumidity();
            display();
        }
    }

    @Override
    public void display() {
        System.out.println("目前状况:" + temperature + " 摄氏度," + humidity + "% 湿度");
    }
}

测试

public class Main {
    public static void main(String[] args) {
        WeatherData weatherData = new WeatherData();
        CurrentConditionsDisplay display1 = new CurrentConditionsDisplay(weatherData);
        System.out.println("通知前");
        display1.display();
        System.out.println("第一次通知后");
        weatherData.mock(25, 60, 30.4f);
        System.out.println("第二次通知后");
        weatherData.mock(20, 72, 41.7f);
    }
}

输出结果和上面一致:
通知前
目前状况:0.0 摄氏度,0.0% 湿度
第一次通知后
目前状况:25.0 摄氏度,60.0% 湿度
第二次通知后
目前状况:20.0 摄氏度,72.0% 湿度

如果有多个不同的公告板,上面输出的结果顺序可能会不同,因为 Observable 类通知的先后顺序不依赖于注册的先后。比如 A、B 都订了同一份报纸,并且 A 比 B 先订阅,但派送新报纸时,可能 A 先收到,可能 B 先收到,与注册先后无关,这是松耦合的体现。

注意:WeatherData 类是通过继承 Observable 类来获得可被观察的行为的,这违背了设计原则的“多用组合,少用继承”。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值