引言

观察者模式可以说是JDK中使用最多的模式之一,这个模式可以让你的对象随时随地的了解想要知道的情况,你还可以让某个对象在运行时决定是否要继续通知;观察者模式的定义为:

定义对象间一种一对多的依赖关系,使得当每一个对象改变状态,则所有依赖于它的对象都会得到通知并自动更新

从定义就可以看出,观察者模式主要就是两部分:主题(Subject)和观察者(Observer)

为了更清楚的了解观察者模式,下面将会用案例说明观察者模式;

天气预报项目

今天一个新的项目交到了你的手上,这个项目是一个天气预报公司的,他们想要

  • 将每天测量到的温度,湿度,气压等等以公告的形式发布出去
  • 设计开放型 API,便于其他第三方也能接入气象站获取数据
  • 而且天气预报最基本的要求是准时,不能开始下雨了才把下雨的预报发出来,所以很重要的一点是:每当天气预报更新的时候,要实时通知给第三方;

通过需求分析可以提炼出关键一点:公布天气信息的“布告板”可以有多个,这些布告板全部从天气预报公司获取天气信息;

大致是下图的意思:

java 使用观察者模式来实时检验会员过期 观察者模式uml_观察者模式


再加上实时更新的需求,就是对标观察者模式;

上面图可以等同于下面这个:

java 使用观察者模式来实时检验会员过期 观察者模式uml_观察者模式_02


所以只需要确定好观察者模式的两个主要组成:主题(Subject)和观察者(Observer)就好了;

很明显,主题对象就是天气预报公司,观察者就是一系列第三方网站(的布告板)

接下来就是如何用方法将观察者和主题联系起来呢?
对于观察者来说,需要的方法就是一个:

  • 接受主题的输入(update)

对于主题来说,需要以下功能:

  • registerObserver() 注册
  • removeObserver() 移除
  • notifyObservers() 通知注册的用户

那么就可以简单尝试画出UML类图了:

java 使用观察者模式来实时检验会员过期 观察者模式uml_百度_03


大致就是这样,Subject是主题的接口,Observer是观察者的接口,

可能你会觉得上面明明就说的那么简单,怎么这一下子多了好几个东西

其实这样子的代码耦合度更低,而且思路非常简单,下面就来通过代码实现一下就明白了;

主题:

// 主题接口
public interface Subject {
    public void registerObserver(Observer o); // 注册观察者
    public void removeObserver(Observer o); // 移除观察者
    public void notifyObserver(); // 通知观察者
}
// 实现主题接口,该类为天气预报公司
import java.util.ArrayList;

public class WeatherData implements Subject {
    private ArrayList<Observer> observers; // 观察者数组,存放所有观察者
    private float temperature; // 温度
    private float humidity; // 湿度
    private float pressure; // 压力
    public WeatherData() {
        observers = new ArrayList<>();
    }
    // 注册观察者
    @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(o);
        }
    }
    // 通知观察者
    @Override
    public void notifyObserver() {
        for (var i : observers) {
            i.update(this.temperature, this.humidity, this.pressure);
        }
    }
    // 信息更新
    public void measurementsChanged() {
        notifyObserver();
    }
    // 设置天气
    public void setMeasurements(float temperature, float humidity, float pressure) {
        this.temperature = temperature;
        this.humidity = humidity;
        this.pressure = pressure;
        measurementsChanged(); // 设置完成天气就已经改变了
    }
}

观察者:

// 观察者
public interface Observer {
    public void update(float temp, float humidity, float pressure); // 更新布告板
    public void display(); // 展示布告板
}
// 天气预报公司自己的布告板
public class CurrentConditionsDisplay implements Observer{
    private float temperature; // 温度
    private float humidity; // 湿度
    private float pressure; // 压力
    @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("当前温度:" + this.temperature +
                "当前湿度:" + this.humidity + "当前压力:" + this.pressure);
    }
}
// 百度网站的温度布告板
public class BaiDuDisplay implements Observer{
    private float temperature; // 温度
    private float humidity; // 湿度
    private float pressure; // 压力
    @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("百度温度:" + this.temperature +
                "百度湿度:" + this.humidity + "百度压力:" + this.pressure);
    }
}

还可以继续增加布告板,这里就列举这两个了;

测试执行代码:

public class WeatherStation {
    public static void main(String[] args) {
        WeatherData weatherData = new WeatherData();
        Observer current = new CurrentConditionsDisplay(); // 自己网站的观察者
        Observer baidu = new BaiDuDisplay(); // 百度观察者
        weatherData.registerObserver(current); // 注册观察者
        weatherData.registerObserver(baidu); // 注册观察者
        weatherData.setMeasurements(11, 22, 33);
    }
}

运行结果:

当前温度:11.0当前湿度:22.0当前压力:33.0
百度温度:11.0百度湿度:22.0百度压力:33.0

其实一看代码,还是两大部分,所以对于观察者模式,主要思考的是主题和观察者的关系就可以了;

使用了观察者模式后,代码有以下好处:

  • 观察者模式设计后,会以集合的方式来管理用户(Observer),包括注册,移除和通知
  • 增加观察者(即成一个新的公告板),就不需要去修改核心类 WeatherData 的代码,遵守 ocp (开闭)原则

Java内置观察者模式

JDK中提供了 java.util.Observable 实现类和 java.util.Observer 接口,这是Java内置的观察者模式,可以直接来用,先简单对比介绍一下:

  • Observable 的作用和地位等价于前面的Subject
  • Observable 是类,不是接口,类中已经实现了核心的方法 ,即管理 Observer 的方法 add… delete … notify…
  • Observer 接口的作用等价于前面的 Observer接口, 都有update
  • Observable类 和 Observer接口 的使用方法和前面讲过的一样,只是 Observable 是类,通过继承来实现观察者模式

下面我将用内置观察者模式支持来重写一下上面的代码实现相同的功能;

主题:

import java.util.Observable;

// 直接继承Observable类
public class WeatherData extends Observable {
    private float temperature; // 温度
    private float humidity; // 湿度
    private float pressure; // 压力
    // 温度改变通知观察者
    public void measurementsChanged() {
        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;
    }
}

观察者:

// 天气预报公司自己的布告板
import java.util.Observable;
import java.util.Observer;

// 实现了Observer接口
public class CurrentConditionsDisplay implements Observer{
    private float temperature; // 温度
    private float humidity; // 湿度
    private float pressure; // 压力
    public void display() {
        System.out.println("当前温度为:" + this.temperature + " 当前湿度为:"
                + this.humidity + " 当前压力为:" + this.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();
        }
    }
}
// 百度网站的温度布告板
import java.util.Observable;
import java.util.Observer;

// 实现了Observer接口
public class BaiDuDisplay implements Observer {
    private float temperature; // 温度
    private float humidity; // 湿度
    private float pressure; // 压力
    public void display() {
        System.out.println("百度温度为:" + this.temperature + " 百度湿度为:"
                + this.humidity + " 百度压力为:" + this.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();
        }
    }
}

测试执行代码:

public class WeatherStation {
    public static void main(String[] args) {
        WeatherData weatherData = new WeatherData(); // 主题
        CurrentConditionsDisplay currentConditionsDisplay = new CurrentConditionsDisplay(); // 观察者
        BaiDuDisplay baiDuDisplay = new BaiDuDisplay(); // 百度观察者
        weatherData.addObserver(currentConditionsDisplay); // 增添观察者
        weatherData.addObserver(baiDuDisplay);// 增添观察者
        weatherData.setMeasurements(11, 22, 33);
    }
}

运行结果:

百度温度为:11.0 百度湿度为:22.0 百度压力为:33.0
当前温度为:11.0 当前湿度为:22.0 当前压力为:33.0

是不是有很多相同的地方,归根结底都是使用的观察者模式,所以只要知道观察者模式由什么组成,什么时候用,那么你就真正了解了观察者模式;

但是设计模式可不是随便看看这俩代码就能会用的,这也是设计模式不好学的一点,还是需要大量的代码练习去体会,最后就能灵活运用;

注:在JDK中JavaBeans和Swing都实现了观察者模式,感兴趣可以尝试一下;

总结

总结一下观察者模式:

  • 观察者模式定义了对象之间一对多的关系
  • 主题通过一个共同的接口来更新观察者
  • 观察者和主题之间用松耦合的方式结合,主题不知道观察者的细节,只知道观察者实现了观察者接口
  • 当有多个观察者时,不能依赖特定的通知次序

最后在这里展示一下观察者模式的UML类图:

java 使用观察者模式来实时检验会员过期 观察者模式uml_观察者模式_04

在这篇文章中的例子的代码只是为了展示观察者模式,但是并不是最优的代码,一个好的代码可能要用到多种设计模式,这也是设计模式的一个难点;
设计模式的学习没有什么技巧,只能在敲代码的过程中不断去体会,可能某一天你就会不自觉的使用设计模式,还是要多敲代码多踩坑多思考,希望我们一起努力!!