观察者模式
观察者模式(Observer):定义了对象之间的一对多关系,当一个对象改变状态时,它的所有依赖者都会收到通知并自动更新。
实现观察者模式的方法有多种,但是以包含Subject与Observer接口的类设计的做法最常见,下面看看观察者模式的类图:
主题(Subject)是真正拥有数据的人,观察者则是主题的依赖者,在主题数据变化时接收通知并更新。这样比起让许多对象控制同一份数据来,可以得到更干净的OO设计。
主题与观察者之间是松耦合的,它们可以交互,但不知道彼此的细节。比如对于观察者,主题只知道观察者实现了某个接口,主题不需要知道观察者的具体类是谁、做了些什么或其他任何细节。任何时候我们都可以增加新的观察者,因为主题唯一依赖的东西是一个实现Observer接口的对象列表;同样的,我们也可以在任何时候删除某些观察者。因为松耦合的关系,改变主题或观察者其中一方,并不会影响另一方。松耦合的设计能让我们建立有弹性的OO系统以应对变化,因为对象之间的互相依赖降到了最低。
下面就以经典的气象监测问题为例,说说观察者模式的应用:
此气象站系统只包括三个部分:气象站、WeatherData对象、布告板。
工作的流程:WeatherData对象从气象站获取最新的测量数据(温度、湿度、气压),并及时更新到三个布告板(显示装置)上。
假设从气象站获取数据的方法已经实现好了,那么我们只需要考虑如何将新的数据更新到三个布告板上,而且要尽量实现系统可拓展,让其他开发人员可以定制布告板,用户可以任意的添加或删除布告板,那么我们如何建立这个系统呢?
当然是使用观察者模式,这里的WeatherData类正是观察者模式中的“一”,即主题;而布告板就是“多”,即观察者;这样就建立起了一对多的依赖关系。WeatherData对象是真正拥有数据的一方,包括温度、湿度、气压,当这些值改变时,必须通知所有的布告板,好让它们各自做出处理。在这里,布告板作为Observer为了获取数据,必须先向WeatherData对象注册,一旦WeatherData知道有某个布告板的存在,就会适时地调用布告板的某个公共的接口(例如. update)来告诉布告板观测值是多少。由于update()方法是所有布告板公共的接口,所以需要在布告板的基类(Java中说接口)中定义。下面是设计图:
- WeatherData实现主题(Subject)接口
- 布告板实现观察者(Observer)接口,这样主题在需要通知观察者时,就有了一个共同的接口
- 同时还为布告板建立一个共同的接口DisplayElement,用于实现display()方法
- 每个布告板中应该声明一个Subject接口类对象
C++实现:
// 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; };
// Observer接口类 #pragma once class Observer { public: virtual void update(float temp, float humidity, float pressure) = 0; };
// 接口类 用于display #pragma once class DisplayElement { public: virtual void display() = 0; };
// 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; }
// “目前状况”布告板 实现观察者接口 #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; }
// 数据统计布告板 #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; }
// 天气预报布告板 #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; }
// 程序入口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; }
运行结果: