《Head-First-设计模式》笔记2-观察者模式

定义

先来看个报纸和杂志的订阅是怎么回事:

  1. 报社的业务就是出版报纸
  2. 向某家报社订阅报纸,只有新报纸出版,报社才会送给你,只要你是用户,他就会一直向你发送。
  3. 当你不向看报纸了,取消订阅,他们就不会发送报纸。
  4. 只要报社还在运营,就会有人订阅报纸或取消报纸订阅

其实这个过程就和我们的观察者模式的过程相似。所以我们可以简单的将观察者模式定义为:出版社 + 订阅者 = 观察者模式

正式的定义:

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

需求

一个天气信息类,当天气信息发生变化是,会自动运行 update() 函数,此时,需要及时更新当前信息版,和未来预测版。

一个错误示范

public class WrongWeatherData {

    public void measurementsChanged() {
        float temp = getTemperature();
        float humidity = getHumidity();
        float pressure = getPressure();

        // 更新操作
        currentConditionsDisplay.update(temp, humidity, pressure);
    }
}

存在的问题:

  1. 它针对实现编程,没有针对接口,这样导致每次增加布告板都要修改里面的代码。
  2. 可能改变的地方没有封装起来(三个更新方法直接在 measurementsChanged() 中)。
  3. 这样根本不可能在运行时动态添加删除一些布告板。

观察者模式:类图

观察者模式:类图

松耦合

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

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

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

有新类型的观察者出现时,主题的代码不需要修改。假如我们有个新的具体类需要当观察者,我们不需要为了兼容新类型而修改主题的代码,所有要做的就是在新的类里实现此观察者接口,然后注册为观察者即可。主题不在乎别的,它只会发送通知给所有实现了观察者接口的对象。

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

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

代码实现

实现气象站,建立接口

public interface Subject {
    public void registerObserver(Observer o);
    public void removeObserver(Observer o);
    public void notifyObservers();
}

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

/**
 * 布告栏公共接口
 */
public interface DisplayElement {
    // 当布告栏需要显示时调用
    public void display();
}

在 WeatherData 中实现主题接口

public class WeatherData implements Subject {
    private ArrayList observers; // 记录观察者
    private float temperature;
    private float humidity;
    private float pressure;

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

    // 设置测量值,并通知观察者.
    public void setMeasurements(float temp, float humidity, float pressure) {
        this.temperature = temp;
        this.humidity = humidity;
        this.pressure = pressure;
        measurementsChanged();
    }

    // 当从气象站得到更新观测值时,我们通知观察者.
    public void measurementsChanged() {
        notifyObservers();
    }

    @Override
    public void notifyObservers() {
        // 通知观察者
        for (int i = 0; i < observers.size(); i++) {
            Observer observer = (Observer) observers.get(i);
            observer.update(temperature, humidity, pressure);
        }
    }

    @Override
    public void registerObserver(Observer o) {
        observers.add(o); // 注册观察者
    }

    @Override
    public void removeObserver(Observer o) {
        int i = observers.indexOf(o);
        if (i >= 0) {
            observers.remove(i); // 取消注册
        }
    }

}

建立布告板

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

    public CurrentDisplay(Subject weatherData) {
        this.weatherData = weatherData;
        weatherData.registerObserver(this);
    }

    // 当update() 被调用时,我们把温度和湿度保存起来,然后调用display。
    @Override
    public void update(float temp, float humidity, float pressure) {
        this.temperature = temp;
        this.humidity = humidity;
        this.pressure = pressure;
        display();
    }

    @Override
    public void display() {
        System.out.println("current contions: temperature is " + temperature
                + ", humidity is " + humidity + ", pressure is " + pressure);
    }
}

测试程序

public class WeatherStation {
    public static void main(String[] args) {
        WeatherData data = new WeatherData();
        CurrentDisplay display = new CurrentDisplay(data);
        data.setMeasurements(1, 23, 30.4f);
    }
}/* output:
current contions: temperature is 1.0, humidity is 23.0, pressure is 30.4
*/

推(push),拉(pull)

上述的观察者模式,由 subject 主动送出自己的状态通知给观察者,推(push)。

subject 提供一些公开的 getter 方法,观察者 “拉(pull)”走自己需要的状态。这样如果以后新增了更多的状态,就不用修改 subject 对 观察者的调用。

Java 内置的的观察者模式

java.util 包内包含最基本的 Observer 接口和 Observable 类,你甚至可以使用 推(push)或拉(pull)的方式传送数据。

Java 内置观察者模式的类图结构

Java 内置观察者模式的类图结构

Observable 类

  • void addObserver(Observer o):如果观察者与集合中已有的观察者不同,则向对象的观察者集中添加此观察者。
  • protected void clearChanged():指示对象不再改变,或者它已对其所有的观察者通知了最近的改变,所以 hasChanged 方法将返回 false。
  • int countObservers():返回 Observable 对象的观察者数目。
  • void deleteObserver(Observer o):从对象的观察者集合中删除某个观察者。
  • void deleteObservers():清除观察者列表,使此对象不再有任何观察者。
  • boolean hasChanged():测试对象是否改变。
  • void notifyObservers():如果 hasChanged 方法指示对象已改变,则通知其所有观察者,并调用 clearChanged 方法来指示此对象不再改变。
  • void notifyObservers(Object arg):如果 hasChanged 方法指示对象已改变,则通知其所有观察者,并调用 clearChanged 方法来指示此对象不再改变。
  • protected void setChanged():标记此 Observable 对象为已改变的对象;现在 hasChanged 方法将返回 true。

内置的代码实现

WeatherData

public class WeatherData extends Observable {
    private float temperature;
    private float humidity;
    private float pressure;

    public WeatherData() {
    }

    public void measurementsChanged() {
        setChanged(); // 标记此 Observable 对象为已改变的对象
        notifyObservers();
    }

    public void setMeasurements(float temp, float humidity, float pressure) {
        this.temperature = temp;
        this.humidity = humidity;
        this.pressure = pressure;
        measurementsChanged();
    }

    public float getTemperature() {
        return temperature;
    }

    public float getHumidity() {
        return humidity;
    }

    public float getPressure() {
        return pressure;
    }
}

重建布告栏

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

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


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

    @Override
    public void display() {
        System.out.println("current contions: temperature is " + temperature + ", humidity is " + humidity);
    }
}

java.util.Observable 存在的问题

  1. Observable 是一个类
  2. Observable 将关键的方法保护起来了,这样就只能继承 Observable,违反了第二个设计原则:“多用组合,少用继承”。
  3. notifyObservers 方法中调用 update 时,查看源码得知:
for (int i = arrLocal.length-1; i>=0; i--)
    ((Observer)arrLocal[i]).update(this, arg);

在循环遍历观察者让观察者做出响应时,如果在这过程中有一个 update 方法抛出了异常,那么剩下还未通知的观察者就全都通知不到了。所以在 观察者中的 update 方法整个加上 try 块,或者确认不会发生运行时异常。

总结

观察者模式,可以从被观察者处推(push)或拉(pull)数据(推的方式被认为更“正确”)。

有多个观察者时,不可以依赖特定的通知次序。

优缺点

http://design-patterns.readthedocs.io/zh_CN/latest/behavioral_patterns/observer.html#id10

优点

  • 观察者模式可以实现表示层和数据逻辑层的分离,并定义了稳定的消息更新传递机制,抽象了更新接口,使得可以有各种各样不同的表示层作为具体观察者角色。
  • 观察者模式在观察目标和观察者之间建立一个抽象的耦合。
  • 观察者模式支持广播通信。
  • 观察者模式符合“开闭原则”的要求。

缺点

  • 如果一个观察目标对象有很多直接和间接的观察者的话,将所有的观察者都通知到会花费很多时间。
  • 如果在观察者和观察目标之间有循环依赖的话,观察目标会触发它们之间进行循环调用,可能导致系统崩溃。
  • 观察者模式没有相应的机制让观察者知道所观察的目标对象是怎么发生变化的,而仅仅只是知道观察目标发生了变化。


转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 bin07280@qq.com

文章标题:《Head-First-设计模式》笔记2-观察者模式

文章字数:2.1k

本文作者:Bin

发布时间:2018-07-07, 16:36:28

最后更新:2019-08-06, 00:07:35

原始链接:http://coolview.github.io/2018/07/07/Head-First-%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/%E3%80%8AHead-First-%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E3%80%8B%E7%AC%94%E8%AE%B02-%E8%A7%82%E5%AF%9F%E8%80%85%E6%A8%A1%E5%BC%8F/

版权声明: "署名-非商用-相同方式共享 4.0" 转载请保留原文链接及作者。

目录