观察者模式

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

实现观察者模式的方法有多种,但是以包含Subject与Observer接口的类设计的做法最常见,下面看看观察者模式的类图:

主题(Subject)是真正拥有数据的人,观察者则是主题的依赖者,在主题数据变化时接收通知并更新。这样比起让许多对象控制同一份数据来,可以得到更干净的OO设计。

主题与观察者之间是松耦合的,它们可以交互,但不知道彼此的细节。比如对于观察者,主题只知道观察者实现了某个接口,主题不需要知道观察者的具体类是谁、做了些什么或其他任何细节。任何时候我们都可以增加新的观察者,因为主题唯一依赖的东西是一个实现Observer接口的对象列表;同样的,我们也可以在任何时候删除某些观察者。因为松耦合的关系,改变主题或观察者其中一方,并不会影响另一方。松耦合的设计能让我们建立有弹性的OO系统以应对变化,因为对象之间的互相依赖降到了最低。

下面就以经典的气象监测问题为例,说说观察者模式的应用:

此气象站系统只包括三个部分:气象站、WeatherData对象、布告板。

工作的流程:WeatherData对象从气象站获取最新的测量数据(温度、湿度、气压),并及时更新到三个布告板(显示装置)上。

假设从气象站获取数据的方法已经实现好了,那么我们只需要考虑如何将新的数据更新到三个布告板上,而且要尽量实现系统可拓展,让其他开发人员可以定制布告板,用户可以任意的添加或删除布告板,那么我们如何建立这个系统呢?

当然是使用观察者模式,这里的WeatherData类正是观察者模式中的“一”,即主题;而布告板就是“多”,即观察者;这样就建立起了一对多的依赖关系。WeatherData对象是真正拥有数据的一方,包括温度、湿度、气压,当这些值改变时,必须通知所有的布告板,好让它们各自做出处理。在这里,布告板作为Observer为了获取数据,必须先向WeatherData对象注册,一旦WeatherData知道有某个布告板的存在,就会适时地调用布告板的某个公共的接口(例如. update)来告诉布告板观测值是多少。由于update()方法是所有布告板公共的接口,所以需要在布告板的基类(Java中说接口)中定义。下面是设计图:

  • WeatherData实现主题(Subject)接口
  • 布告板实现观察者(Observer)接口,这样主题在需要通知观察者时,就有了一个共同的接口
  • 同时还为布告板建立一个共同的接口DisplayElement,用于实现display()方法
  • 每个布告板中应该声明一个Subject接口类对象

C++实现:

1
2
3
4
5
6
7
8
9
10
11
// Subject接口类
#pragma once
#include "Observer.h"

class Subject
{
public:
virtual void registerObserver(Observer* o) = 0;
virtual void removeObserver(Observer* o) = 0;
virtual void notifyObserver() = 0;
};
1
2
3
4
5
6
7
// Observer接口类
#pragma once
class Observer
{
public:
virtual void update(float temp, float humidity, float pressure) = 0;
};
1
2
3
4
5
6
7
// 接口类 用于display
#pragma once
class DisplayElement
{
public:
virtual void display() = 0;
};
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
// WeatherData实现Subject接口
#pragma once
#include "Observer.h"
#include "Subject.h"
#include <list>

class WeatherData : public Subject
{
private:
mutable std::list<Observer*> observers;
float temperature;
float humidity;
float pressure;

public:
WeatherData(void);
~WeatherData(void);
void registerObserver(Observer* o);
void removeObserver(Observer* o);
void notifyObserver();
void measurementsChanged();
void setMeasurements(float temperature, float humidity, float pressure);
// 以下方法用于从气象台获取数据,这里不予考虑
float getTemperature();
float getHumidity();
float getPressure();
};


// WeatherData.cpp
#include "WeatherData.h"
using namespace std;

void WeatherData::registerObserver( Observer* o )
{
observers.push_back(o);
}

void WeatherData::removeObserver( Observer* o )
{
observers.remove(o);
}

void WeatherData::notifyObserver()
{
for (list<Observer*>::iterator iterator=observers.begin(); observers.end()!=iterator; ++iterator)
{
Observer* observer = *iterator;
observer->update(temperature, humidity, pressure);
}
}

void WeatherData::measurementsChanged()
{
notifyObserver();
}

void WeatherData::setMeasurements( float temperature, float humidity, float pressure )
{
this->temperature = temperature;
this->humidity = humidity;
this->pressure = pressure;
measurementsChanged();
}

float WeatherData::getTemperature()
{
return temperature;
}

float WeatherData::getHumidity()
{
return humidity;
}

float WeatherData::getPressure()
{
return pressure;
}
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
// “目前状况”布告板 实现观察者接口
#pragma once
#include "WeatherData.h"
#include "Observer.h"
#include "DisplayElement.h"

class CurrentConditionsDisplay : public Observer, public DisplayElement
{
private:
float temperature;
float humidity;
Subject* weatherData;

public:
CurrentConditionsDisplay(Subject* weatherData);
~CurrentConditionsDisplay(void);
void update(float temp, float humidity, float pressure);
void display();
};

// CurrentConditionsDisplay.cpp
#include "CurrentConditionsDisplay.h"
#include <iostream>
using namespace std;


CurrentConditionsDisplay::CurrentConditionsDisplay( Subject* weatherData )
{
this->weatherData = weatherData; // 构造器中注册为观察者
weatherData->registerObserver(this);
}

CurrentConditionsDisplay::~CurrentConditionsDisplay(void)
{
weatherData->removeObserver(this);
}

void CurrentConditionsDisplay::update( float temp, float humidity, float pressure )
{
this->temperature = temp;
this->humidity = humidity;
display();
}

void CurrentConditionsDisplay::display()
{
cout.setf( ios::showpoint );
cout.precision(3);
cout << "Current conditions: " << temperature;
cout << " F degrees and " << humidity;
cout << "% humidity" << endl;
}
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
// 数据统计布告板
#pragma once
#include "WeatherData.h"
#include "Observer.h"
#include "DisplayElement.h"

class StatisticsDisplay : public Observer, public DisplayElement
{
private:
Subject* weatherData;
float maxTemp;
float minTemp;
float tempSum;
int numReadings;

public:
StatisticsDisplay(Subject* weatherData);
~StatisticsDisplay(void);
void update(float temp, float humidity, float pressure);
void display();
};


// StatisticsDisplay.cpp
#include "StatisticsDisplay.h"
#include <iostream>
using namespace std;

StatisticsDisplay::StatisticsDisplay(Subject* weatherData)
{
maxTemp = 0.0; // 记录最高温
minTemp = 200.0F; // 记录最低温
tempSum = 0.0; // 温度和,用于计算平均温度
numReadings = 0; // 次数,用于计算平均温度
this->weatherData = weatherData; // 构造器中注册为观察者
weatherData->registerObserver(this);
}


StatisticsDisplay::~StatisticsDisplay(void)
{
weatherData->removeObserver(this);
}

void StatisticsDisplay::update( float temp, float humidity, float pressure )
{
tempSum += temp;
numReadings++;
if( temp > maxTemp ) {
maxTemp = temp;
}

if( temp < minTemp ) {
minTemp = temp;
}
display();
}

void StatisticsDisplay::display()
{
cout.setf( ios::showpoint );
cout.precision(3);
cout << "Avg/Max/Min temperature = " << ( tempSum / numReadings );
cout << "/" << maxTemp << "/" << minTemp << endl;
}
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
// 天气预报布告板
#pragma once
#include "WeatherData.h"
#include "Observer.h"
#include "DisplayElement.h"

class ForecastDisplay : public Observer, public DisplayElement
{
private:
float currentPressure;
float lastPressure;
Subject* weatherData;

public:
ForecastDisplay(Subject* weatherData);
~ForecastDisplay(void);
void update(float temp, float humidity, float pressure);
void display();
};


// ForecastDisplay.cpp
#include "ForecastDisplay.h"
#include <iostream>
using namespace std;

ForecastDisplay::ForecastDisplay(Subject* weatherData)
{
currentPressure = 29.92F; // 假设现在的
lastPressure = 0.0;
this->weatherData = weatherData; // 构造器中注册为观察者
weatherData->registerObserver(this);
}


ForecastDisplay::~ForecastDisplay(void)
{
weatherData->removeObserver(this);
}

void ForecastDisplay::update( float temp, float humidity, float pressure )
{
lastPressure = currentPressure;
currentPressure = pressure;
display();
}

void ForecastDisplay::display()
{
cout << "Forecast: ";
if( currentPressure > lastPressure ) {
cout << "Improving weather on the way!";
} else if( currentPressure == lastPressure ) {
cout << "More of the same";
} else if( currentPressure < lastPressure ) {
cout << "Watch out for cooler, rainy weather";
}
cout << endl;

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 程序入口WeatherStation.cpp
#include "WeatherData.h"
#include "CurrentConditionsDisplay.h"
#include "ForecastDisplay.h"
#include "StatisticsDisplay.h"

int main(){
WeatherData* weatherData = new WeatherData;

CurrentConditionsDisplay* cu = new CurrentConditionsDisplay(weatherData);
StatisticsDisplay* st = new StatisticsDisplay(weatherData);
ForecastDisplay* fo = new ForecastDisplay(weatherData);
// 更新数据,这里手动模拟
weatherData->setMeasurements( 80, 65, 30.4f );
weatherData->setMeasurements( 82, 70, 29.2f );
weatherData->setMeasurements( 78, 90, 29.2f );

getchar();
return 0;
}

运行结果: