使用Java内置的观察者模式

这是我参与11月更文挑战的第15天,活动详情查看:2021最后一次更文挑战

使用Java内置的观察者模式

Java中内置了观察者模式,在 java.util 包中内置了Observer接口和Observable类,使用方法与Observer接口和Subject接口相似,但这个内置的观察者模式还有着一些上面我们没有实现的功能 —— 获取数据的主动权和知道数据来源的功能。

Observable类源码

/*
 * Copyright (c) 1994, 2012, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 */

package java.util;

public class Observable {

    // changed时判断是否需要进行通知的标识
    private boolean changed = false;
    // 	观察者
    private Vector<Observer> obs;
    
    public Observable() {
        obs = new Vector<>();
    }
    /**
     * 添加一个观察者
     */
    public synchronized void addObserver(Observer o) {
        if (o == null)
            throw new NullPointerException();
        if (!obs.contains(o)) {
            obs.addElement(o);
        }
    }
    /**
     * 删除
     */
    public synchronized void deleteObserver(Observer o) {
        obs.removeElement(o);
    }
    /**
     * notifyObservers()方法可以在具体的观察对象来自定义,无参数则通知全部
     */
    public void notifyObservers() {
        notifyObservers(null);
    }
    /**
     * notifyObservers()在有对应观察者作为参数时,只传给该观察者
     */
    public void notifyObservers(Object arg) {
        Object[] arrLocal;
        synchronized (this) {
            if (!changed)
                return;
            arrLocal = obs.toArray();
            clearChanged();
        }
        for (int i = arrLocal.length-1; i>=0; i--)
            ((Observer)arrLocal[i]).update(this, arg);
    }

    public synchronized void deleteObservers() {
        obs.removeAllElements();
    }
    /**
     * 当要通知观察者时需要进行此操作
     */
    protected synchronized void setChanged() {
        changed = true;
    }

    protected synchronized void clearChanged() {
        changed = false;
    }

    public synchronized boolean hasChanged() {
        return changed;
    }

    public synchronized int countObservers() {
        return obs.size();
    }
}
复制代码

Observable超类中实现了对观察者的管理具体方法,也拥有了观察者登记表,这样使得我们在编写具体观察者时可以直接使用父类的方法进行基本的管理

Observer接口源码

/*
 * Copyright (c) 1994, 1998, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 */
package java.util;

public interface Observer {
    void update(Observable o, Object arg);
}
复制代码

与我们编写的Observer接口类似,但这里规定了一个观察对象参数和观察量参数,这样观察者就可以知道这是哪个观察对象传来的数据。

实现

首先我们实现具体的被观察者

  • 天气参数、当前参数
  • 参数发生变化调用的方法
public class WeatherData extends Observable{

    /**
     * 天气数据
     */
    private float temperature;
    private float humidity;
    private float pressure;

    /**
     * 实时的天气数据
     */
    private float realTemperature;
    private float realHumidity;
    private float realPressure;

    WeatherData() {
    }

    /**
     * 数据改变后measurementsChanged先被调用,判断是否更新当前数据,更新则然后执行setChange()
     *
     */
    void measurementsChanged() {
        // 温差不超过2度时不更新数据,避免数据频繁更新
        if (abs(realTemperature-this.temperature)>2) {
            // 将changed参数改为true,此时notifyObservers()内部可以被执行
            setChanged();
            System.out.println("changed");
            this.temperature = realTemperature;
            this.humidity = realHumidity;
            this.pressure = realPressure;
        } else {
            System.out.println("too Little");
        }
        // 遍历观察者列表进行update,在观察者中重写update
        notifyObservers();
    }

    /**
     * 用于测试的改变数据
     */
    void setMeasurements(float temperature, float humidity, float pressure) {
        this.realTemperature = temperature;
        this.realHumidity = humidity;
        this.realPressure = pressure;
    }

    /**
     * 对外提供获取数据的接口
     */
    float getTemperature() {
        return temperature;
    }

    float getHumidity() {
        return humidity;
    }

    public float getPressure() {
        return pressure;
    }
}
复制代码

具体观察者

public class CurrentConditionDisplay implements Observer, Display {
    private float temperature;
    private float humidity;

    CurrentConditionDisplay(Observable observable) {
        observable.addObserver(this);
    }

    /**
     * 这里的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();
        }
    }

    @Override
    public void display() {
        System.out.println("temperature: " + temperature + ", humidity: " + humidity);
    }
}
复制代码

测试

public class WeatherStation {
    
    public static void main(String[] args) {
        // 被订阅的主题:天气数据
        WeatherData weatherData = new WeatherData();
        // 展示板订阅这个天气主题
        CurrentConditionDisplay currentConditionDisplay = new CurrentConditionDisplay(weatherData);
        // 设置天气变量
        weatherData.setMeasurements(25, 60, 10600);
        // 天气变量发生改变
        weatherData.measurementsChanged();
        weatherData.setMeasurements(29, 45, 10050);
        weatherData.measurementsChanged();
        weatherData.setMeasurements(30, 45, 10050);
        weatherData.measurementsChanged();
        // 取消订阅
        weatherData.deleteObserver(currentConditionDisplay);
        weatherData.setMeasurements(25, 60, 10600);
        weatherData.measurementsChanged();
    }
}
复制代码

结果

image.png

总结

模式结构

UML图

image.png

时序图

image.png

设计原则

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

  • 观察者模式提供了一种让两个对象实现松耦合的设计

主题只知道观察者实现了Observer接口,他唯一依赖的是Observer接口的对象列表,主题负责对Observer进行管理,向Observer推送数据,他不在乎谁实现了观察者接口,因此在运行时可以随时修改为新的观察者。新的类型的观察者出现时,不需要修改主题代码,只需要新实现一个观察者接口然后向主题注册。这种对主题和观察者的独立复用,源于他们之间松耦合的设计。

缺点

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

应用环境

  • 一个抽象模型有两个方面,其中一个方面依赖于另一个方面。将这些方面封装在独立的对象中使它们可以各自独立地改变和复用。
  • 一个对象的改变将导致其他一个或多个对象也发生改变,而不知道具体有多少对象将发生改变,可以降低对象之间的耦合度。
  • 一个对象必须通知其他对象,而并不知道这些对象是谁。
  • 需要在系统中创建一个触发链,A对象的行为将影响B对象,B对象的行为将影响C对象……,可以使用观察者模式创建一种链式触发机制。

Guess you like

Origin juejin.im/post/7031173527843799047