设计原则4:为了交互对象之间的松耦合设计而努力。
松耦合的设计之所以能让我们建立有弹性的OO系统,能够应对变化,是因为对象之间的互相依赖降到了最低。
观察者模式定义了对象之间的一对多依赖,这样一来,当一个对象改变状态时,它的多有依赖者都会收到通知并自动更新。
定义接口
#include <iostream>
#include <list>
using namespace std;
class Observer {
public:
virtual void update(float temperature, float humidity, float pressure) = 0;
};
class Subject {
public:
virtual void registerObserver(Observer* o) = 0;
virtual void removeObserver(Observer* o) = 0;
virtual void notifyObserver() = 0;
};
class DisplayElement {
public:
virtual void display() = 0;
};
定义被观察者(主题)
使用std::list来代替Java中的ArrayList。list有unique函数和remove函数,使用起来比vector更方便。
class WeatherData :public Subject {
private:
list<Observer*> observers;
float temperature;
float humidity;
float pressure;
public:
void registerObserver(Observer *o) {
observers.push_back(o);
observers.unique();
}
void removeObserver(Observer *o) {
observers.remove(o);
}
void notifyObserver() {
for (list<Observer*>::iterator itor = observers.begin(); itor != observers.end(); ++itor) {
(*itor)->update(temperature, humidity, pressure);
}
}
void measurementsChanged() {
notifyObserver();
}
void setMeasurements(float temp, float humi, float pres) {
temperature = temp;
humidity = humi;
pressure = pres;
measurementsChanged();
}
};
定义观察者,书中定义了4个观察者
class CurrentConditionsDisplay : public Observer, public DisplayElement {
private:
float m_temperature = 0;
float m_humidity = 0;
Subject* m_weatherData;
public:
CurrentConditionsDisplay(Subject *weatherData) {
m_weatherData = weatherData;
m_weatherData->registerObserver(this);
}
~CurrentConditionsDisplay() {
m_weatherData->removeObserver(this);
}
void update(float temperature, float humidity, float pressure) {
m_temperature = temperature;
m_humidity = humidity;
display();
}
void display() {
printf("Current conditions: %fF degrees and %f%% humidity.\n", m_temperature, m_humidity);
}
};
class StatisticsDisplay : public Observer, public DisplayElement {
private:
float maxTemp = 0.0f;
float minTemp = 200;
float tempSum = 0.0f;
int numReadings = 0;
WeatherData* m_weatherData;
public:
StatisticsDisplay(WeatherData* weatherData) {
m_weatherData = weatherData;
m_weatherData->registerObserver(this);
}
~StatisticsDisplay() {
m_weatherData->removeObserver(this);
}
void update(float temp, float humidity, float pressure) {
tempSum += temp;
numReadings++;
if (temp > maxTemp) {
maxTemp = temp;
}
if (temp < minTemp) {
minTemp = temp;
}
display();
}
void display() {
printf("Avg/Max/Min temperature = %f/%f/%f.\n", (tempSum / numReadings), maxTemp, minTemp);
}
};
class ForecastDisplay : public Observer, public DisplayElement {
private:
float currentPressure = 29.92f;
float lastPressure = 29.92f;
WeatherData* m_weatherData;
public:
ForecastDisplay(WeatherData* weatherData) {
m_weatherData = weatherData;
m_weatherData->registerObserver(this);
}
~ForecastDisplay() {
m_weatherData->removeObserver(this);
}
void update(float temp, float humidity, float pressure) {
lastPressure = currentPressure;
currentPressure = pressure;
display();
}
void display() {
printf("Forecast: ");
if (currentPressure > lastPressure) {
printf("Improving weather on the way!\n");
}
else if (currentPressure == lastPressure) {
printf("More of the same\n");
}
else if (currentPressure < lastPressure) {
printf("Watch out for cooler, rainy weather\n");
}
}
};
class HeatIndexDisplay : public Observer, public DisplayElement {
private:
float heatIndex = 0.0f;
WeatherData* m_weatherData;
double computeHeatIndex(double t, double rh) {
double index = (double)((16.923 + (0.185212 * t) + (5.37941 * rh) - (0.100254 * t * rh) +
(0.00941695 * (t * t)) + (0.00728898 * (rh * rh)) +
(0.000345372 * (t * t * rh)) - (0.000814971 * (t * rh * rh)) +
(0.0000102102 * (t * t * rh * rh)) - (0.000038646 * (t * t * t)) + (0.0000291583 *
(rh * rh * rh)) + (0.00000142721 * (t * t * t * rh)) +
(0.000000197483 * (t * rh * rh * rh)) - (0.0000000218429 * (t * t * t * rh * rh)) +
0.000000000843296 * (t * t * rh * rh * rh)) -
(0.0000000000481975 * (t * t * t * rh * rh * rh)));
return index;
}
public:
HeatIndexDisplay(WeatherData* weatherData) {
m_weatherData = weatherData;
m_weatherData->registerObserver(this);
}
~HeatIndexDisplay() {
m_weatherData->removeObserver(this);
}
void update(float temp, float humidity, float pressure) {
heatIndex = computeHeatIndex(temp, humidity);
display();
}
void display() {
printf("Heat Index = %f\n", heatIndex);
}
};
测试
void main() {
WeatherData *weatherData = new WeatherData();
CurrentConditionsDisplay* currentDisplay = new CurrentConditionsDisplay(weatherData);
StatisticsDisplay* statisticsDisplay = new StatisticsDisplay(weatherData);
ForecastDisplay* forecastDisplay = new ForecastDisplay(weatherData);
HeatIndexDisplay* heatIndexDisplay = new HeatIndexDisplay(weatherData);
weatherData->setMeasurements(80, 65, 30.4f);
delete statisticsDisplay;
weatherData->setMeasurements(82, 70, 29.2f);
delete forecastDisplay;
weatherData->setMeasurements(78, 90, 29.2f);
delete heatIndexDisplay;
weatherData->setMeasurements(76, 82, 29.8f);
}
观察者模式的优点:
1. 可以让表示层和数据逻辑层分离,并在主题和观察者之间建立一个抽象的耦合 (低耦合,主题只知道观察者实现了 Observer 接口)
2. 建立了一套触发机制,支持广播式通信
观察者模式的缺点:
1. 如果一个主题对象有很多观察者的话,将所有的观察者都通知到会花费很多时间
2. 如果观察者和主题对象有循环依赖的话,主题对象会触发它们之间进行循环调用,可能导致系统崩溃
3. 没有相应的机制让观察者知道主题对象是怎么发生变化的,而仅仅只是知道主题对象发生了变化
参考:
1. EricFreeman, FreemanElisabeth, 弗里曼, et al. Head First设计模式[M]. 中国电力出版社, 2007.