head first 设计模式读书笔记-观察者模式

观察者模式

需求

有一个气象站,会随时更新气象信息。同时,有各种显示器,会展示这些气象信息。现在需要气象站每次更新数据时可以将新的数据及时推送到显示屏。

代码实现

我们现在有一个 WeatherData(气象站)对象,负责产生新的天气数据,还有一个 CurrentConditionsDisplay(显示屏)对象,用来显示最新的气象数据,这个对象也就是观察者。观察者在气象站注册 (registerObserver) 之后,气象站如果有最新的数据就会及时提醒 ( notifyObservers ) 已经注册的观察者,及时更新显示。如果这个显示屏不想再接收到这个最新的气象数据,气象站可以将这个显示屏从气象站的通知列表中删除 ( removeObserver ) ,下次气象站有数据更新时,将不会通知这个显示屏。

  1. 定义一个借口,规定气象站应该有的功能
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 interface Subject {
/**
* 注册观察者
*
* @param observer
*/
void registerObserver(Observer observer);

/**
* 移除观察者
*
* @param observer
*/
void removeObserver(Observer observer);

/**
* 通知观察者
*/
void notifyObservers();

}

  1. 实现一个气象站
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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
/**
* 定义这个气象站
*/
public class WeatherData implements Subject {
/**
* 注册的观察者列表
*/
private ArrayList<Observer> observers;
/**
* 温度
*/
private float temperature;
/**
* 湿度
*/
private float humidity;
/**
* 气压
*/
private float pressure;

public WeatherData() {

this.observers = new ArrayList<>();

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

}

/**
* 移除观察者
*
* @param observer
*/
@Override
public void removeObserver(Observer observer) {
observers.remove(observers.indexOf(observer));
}


/**
* 提醒观察者
*/
@Override
public void notifyObservers() {
if (observers != null && observers.size() > 0) {
observers.stream().forEach(o -> o.update(temperature, humidity, pressure));
}

}

/**
* 调用提醒方法
*/
public void measurementsChanged() {
notifyObservers();
}

/**
* 气象站设置新的数据,并通知观察者(显示屏)
*
* @param temperature
* @param humidity
* @param pressure
*/
public void setMeasurements(float temperature, float humidity, float pressure) {
this.temperature = temperature;
this.humidity = humidity;
this.pressure = pressure;
measurementsChanged();
}
}
  1. 定义两个接口,规定一个显示屏应该有的功能

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    /**
    * 规定观察者应该有更新自身数据的功能
    */
    public interface Observer {
    void update(float temperature, float humidity, float pressure);
    }
    /**
    * 规定观察者应该有显示的功能
    */
    public interface DisplayElement {
    void display();
    }

  2. 定义两个显示屏,分别显示现在的天气状况和明天的天气状况

    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
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    /**
    * 实时气温显示屏
    */
    public class CurrentConditionsDisplay implements Observer, DisplayElement {

    private float temperature;
    private float humidity;
    private float pressure;
    WeatherData weatherData;

    /**
    * 每次生成一个显示屏,就向气象站注册
    *
    * @param weatherData
    */
    public CurrentConditionsDisplay(WeatherData weatherData) {
    this.weatherData = weatherData;
    weatherData.registerObserver(this);
    }

    @Override
    public void display() {
    System.out.println("现在的天气是: " + temperature + " ℃,相对湿度是:
    " + humidity + "%,大气压是:" + pressure + "kPa");
    }


    /**
    * 更新显示屏显示的数据
    *
    * @param temperature
    * @param humidity
    * @param pressure
    */
    @Override
    public void update(float temperature, float humidity, float pressure) {
    this.temperature = temperature;
    this.humidity = humidity;
    this.pressure = pressure;
    display();
    }
    }

    /**
    * 天气预报显示屏
    */
    public class ForecastDisplay implements Observer, DisplayElement {
    private float temperature;
    private float humidity;
    private float pressure;
    WeatherData weatherData;

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

    @Override
    public void display() {
    System.out.println("现在的天气是: " + temperature +
    " ℃,相对湿度是: " + humidity + "%,大气压是:" + pressure + "kPa");
    }

    @Override
    public void update(float temperature, float humidity, float pressure) {
    this.temperature = temperature;
    this.humidity = humidity;
    this.pressure = pressure;
    display();
    }
    }
  3. 代码测试

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    public class WeatherStationTest {

    @Test
    public void runWeatherStation() {
    // 定义一个气象站
    WeatherData weatherData = new WeatherData();
    // 生成一个 实时温度 显示屏
    CurrentConditionsDisplay currentConditionsDisplay =
    new CurrentConditionsDisplay(weatherData);
    // 生成一个 天气预报显示屏
    ForecastDisplay forecastDisplay = new ForecastDisplay(weatherData);
    // 气象站更新数据两个显示屏都会显示
    weatherData.setMeasurements(25, 60, 101.3f);
    System.out.println("我现在只想看天气预报-----------");
    // 现在将实时气温显示屏移除,以后气象站更新数据,只会通知天气预报显示屏
    weatherData.removeObserver(currentConditionsDisplay);
    weatherData.setMeasurements(26, 55, 101.4f);

    }
    }
  4. 运行效果

    1
    2
    3
    4
    现在的天气是: 25.0 ℃,相对湿度是: 60.0%,大气压是:101.3kPa
    明天的天气是: 25.0 ℃,相对湿度是: 60.0%,大气压是:101.3kPa
    我现在只想看天气预报-----------
    明天的天气是: 26.0 ℃,相对湿度是: 55.0%,大气压是:101.4kPa

使用jdk自带的类来实现上述功能

java.util 包中有两个类 ObservableObserver,可以帮助我们实现上面的功能

  1. 定义气象站的类

    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
    41
    42
    import java.util.Observable;


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


    public WeatherData() {
    }

    /**
    * 调用提醒方法
    */
    public void measurementsChanged() {
    /* 设置 Observable 类中的 changed 属性为 true,只有当 changed 属性为 true 时,
    * notifyObservers 方法内部才会调用各个显示屏的update的方法
    */
    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;
    }
    }
  2. 定义两个显示屏

    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
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    /**
    * 当前气温显示屏
    * DisplayElement 代码同上

    */
    public class CurrentConditionsDisplay implements Observer, DisplayElement {

    Observable observable;
    private float temperature;
    private float humidity;
    private float pressure;


    // 构建一个显示屏时,向气象站注册
    public CurrentConditionsDisplay(Observable observable) {
    this.observable = observable;
    observable.addObserver(this);
    }

    @Override
    public void display() {
    System.out.println("今天的天气是: " + temperature +
    " ℃,相对湿度是: " + humidity + "%,大气压是:" + pressure + "kPa");
    }

    @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;

    /**
    * 天气预报显示屏
    */
    public class ForecastDisplay implements Observer, DisplayElement {
    private float temperature;
    private float humidity;
    private float pressure;
    Observable observable;

    @Override
    public void display() {
    System.out.println("明天的天气是: " + temperature +
    " ℃,相对湿度是: " + humidity + "%,大气压是:" + pressure + "kPa");
    }


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


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

    }
    }

  1. 测试代码

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

    @Test
    public void runWeatherStation2() {
    WeatherData weatherData = new WeatherData();
    CurrentConditionsDisplay currentConditionsDisplay =
    new CurrentConditionsDisplay(weatherData);
    ForecastDisplay forecastDisplay = new ForecastDisplay(weatherData);
    weatherData.setMeasurements(25, 60, 101.1f);
    System.out.println("------现在我们把当前温度移除--------");
    weatherData.deleteObserver(currentConditionsDisplay);
    weatherData.setMeasurements(32, 71, 101.6f);

    }
    }

  2. 运行结果

    1
    2
    3
    4
    明天的天气是: 25.0 ℃,相对湿度是: 60.0%,大气压是:101.1kPa
    今天的天气是: 25.0 ℃,相对湿度是: 60.0%,大气压是:101.1kPa
    ------现在我们把当前温度移除--------
    明天的天气是: 32.0 ℃,相对湿度是: 71.0%,大气压是:101.6kPa
作者

Bruce Liu

发布于

2018-11-17

更新于

2022-11-12

许可协议

You need to set install_url to use ShareThis. Please set it in _config.yml.
You forgot to set the business or currency_code for Paypal. Please set it in _config.yml.

评论

You forgot to set the shortname for Disqus. Please set it in _config.yml.