观察者模式

认识观察者模式

我们看看报纸和杂志社的订阅是怎么回事:

  1. 报社的业务就是出版报纸。
  2. 向某家报社订阅报纸,只要他们有新报纸出版,就会给你送来。只要你是他们的订户,你就会一直收到新报纸。
  3. 当你不想再看报纸的时候,取消订阅,他们就不会再送新报纸来。
  4. 只要报社还在运营,就会一直有人(或单位)向他们订阅报纸或取消订阅报纸。

如果你了解了报纸的订阅是怎么回事,其实就知道观察者模式是怎么回事,只是名称不太一样:出版社改称为“主题(Subject)”,订阅者改称为“观察者(Observer)”。

定义

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

结构图


具体的解释看下图:

实现

假设现在要气象监测的应用,此系统中由三个部分,分别是气象站(获取实际气象数据的物理装置),WeatherData对象(追踪来自气象站的数据,并更新布告板),布告板(显示目前天气状况给用户看)。

WeatherData对象知道如何跟物理气象站联系,以取得更新的数据。WeatherData对象会随即更新三个布告板的显示:目前状况(温度、湿度、气压)、对象统计和天气预报。我们的工作就是建立一个应用,利用WeatherData对象取得数据,并更新三个布告板:目前状况、气象统计和天气预报。

因此根据上面的结构图开始设计这个气象站应用:

实现主题

先建立一个主题接口,提供WeatherData实现的基础:

1
2
3
4
5
6
7
8
9
10
11
12
public interface Subject {
/**
* 这两个方法都需要一个观察者作为参数,该观察者是用来注册或被删除的。
*/
public void registerObserver(Observer o);
public void removeObserver(Observer o);

/**
* 当主题状态改变时,这个方法会被调用,以通知所有的观察者。
*/
public void notifyObservers();
}

然后创建WeatherData类,实现Subject接口,说明它是主题的角色:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
public class WeatherData implements Subject {
private List<Observer> observers;
private float temperature;
private float humidity;
private float pressure;

public WeatherData() {
observers = new LinkedList<>();
}
@Override
public void registerObserver(Observer o) {
observers.add(o);
}

@Override
public void removeObserver(Observer o) {
int i = observers.indexOf(o);
if(i >= 1) {
observers.remove(o);
}
}

@Override
public void notifyObservers() {
for(Observer o : observers) {
o.update(temperature, humidity, pressure);
}
}

public void measurementsChanged() {
notifyObservers();
}

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

这样告示板就可以通过WeatherData这个对象进行注册和删除,而当气象站的数据更新时,就可以调用WeatherData的setMeasurements()方法进行数据更改,而且会自动通知给所有订阅的告示板。

观察者接口

创建一个观察者接口,作为所有观察者的基础,其中只有一个方法update(),用于被通知。

1
2
3
4
5
6
7
8
9
10
11
12
public interface Observer {
/**
* 所有的观察者都必须是实现update()方法,以实现观察者接口。
*
* 当气象观测值改变时,主题会把这些状态值当作方法的参数,传送给观察者。
*
* @param temp
* @param humidity
* @param pressure
*/
public void update(float temperature, float humidity, float pressure);
}

告示板接口

此处为告示板实现基础的接口:DisplayElement,用于标识所实现的类为告示板:

1
2
3
4
5
6
7
public interface DisplayElement {
/**
* DisplayElement接口只包含了一个方法就是display()
* 当布告板需要显示时,调用此方法。
*/
public void display();
}

观察者实现

下面建立三个告示板,分别用来展示目前状况(温度、湿度、气压)、对象统计和天气预报。因此所有的告示板实现类都需要实现Observer接口,以标识它是一个观察者,而且可以接收到通知;并且实现DisplayElement接口,标识它是一个告示板。

  • 目前状况告示板:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    public class CurrentConditionDisplay implements Observer,DisplayElement{

    private float temperature;
    private float humidity;
    private float pressure;
    private Subject weatherData;

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

    @Override
    public void display() {
    System.out.println("CurrentConditionDisplay:温度:" + temperature + "度,湿度:" + humidity + "%,压强:" + pressure);
    }

    @Override
    public void update(float temperature, float humidity, float pressure) {
    this.temperature = temperature;
    this.humidity = humidity;
    this.pressure =pressure;
    display();
    }
    }
  • 对象统计告示板:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    public class StatisticsDisplay implements Observer,DisplayElement {

    private float temperature;
    private float humidity;
    private float pressure;
    private Subject weatherData;


    public StatisticsDisplay(Subject weatherData) {
    this.weatherData = weatherData;
    weatherData.registerObserver(this);
    }
    @Override
    public void display() {
    System.out.println("StatisticsDisplay:温度:" + temperature + "度,湿度:" + humidity + "%,压强:" + pressure);
    }

    @Override
    public void update(float temperature, float humidity, float pressure) {
    this.temperature = temperature;
    this.humidity = humidity;
    this.pressure =pressure;
    display();
    }
    }
  • 天气预报告示板:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    public class ForecastDisplay implements Observer, DisplayElement{
    private float temperature;
    private float humidity;
    private float pressure;
    private Subject weatherData;

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

    @Override
    public void display() {
    System.out.println("ForecastDisplay:温度:" + temperature + "度,湿度:" + humidity + "%,压强:" + pressure);
    }

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

查看结果

下面进行测试:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Main {

public static void main(String[] args) {

WeatherData weatherData = new WeatherData();
CurrentConditionDisplay currentConditionDisplay = new CurrentConditionDisplay(weatherData);
StatisticsDisplay statisticsDisplay = new StatisticsDisplay(weatherData);
ForecastDisplay forecastDisplay = new ForecastDisplay(weatherData);

weatherData.setMeasurements(0, 0, 0);
weatherData.removeObserver(currentConditionDisplay);
System.out.println("气象数据发生变化.....");
weatherData.setMeasurements(1, 1, 1);
}
}

输出的结果如下:

1
2
3
4
5
6
CurrentConditionDisplay:温度:0.0度,湿度:0.0%,压强:0.0
StatisticsDisplay:温度:0.0度,湿度:0.0%,压强:0.0
ForecastDisplay:温度:0.0度,湿度:0.0%,压强:0.0
气象数据发生变化.....
StatisticsDisplay:温度:1.0度,湿度:1.0%,压强:1.0
ForecastDisplay:温度:1.0度,湿度:1.0%,压强:1.0

整体结构

总结

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

主题(Subject)是被观察的对象,而其所有依赖者(Observer)称为观察者。

  • 观察者模式定义了对象之间一对多的关系。
  • 主题(也就是可观察者)用一个共同的接口来更新观察者。
  • 观察者和可观察者之间用松耦合方式结合,可观察者不知道观察者的细节,只知道观察者实现了观察者接口。
  • 使用此模式时,你可从被观察者处推(push)或拉(pull)数据,然而,推的方式被认为更正确。
  • 有多个观察者时,不可以依赖特定的通知次序。
  • Java有多种观察者模式的实现,包括了通用的java.util.Observable。