需求
气象站需要做一个应用,有三种布告板,分别显示目前的状况/气象统计以及简单的预报。气象站已经有了一个WeatherData对象,用来追踪全天的天气状况(温度/湿度/气压)。当WeatherData对象更新时,三种布告板必须实时更新。
系统架构
此系统中的三个部分是气象站(获取实际气象数据的物理装置)、WeatherData对象(追踪来自气象站的数据,并更新布告板)以及布告板(显示目前天气状况给用户看)。
我们的工作就是利用WeatherData对象取得数据,并更新三个布告板:目前状况、气象统计和天气预报。
WeatherData
目前所知道的
- WeatherData类具有getter方法,可以获取三个测量值:温度、湿度和气压;
- 当新的数据就绪时,
measurementsChanged()就会被调用; - 需要实现3个使用天气数据的布告板。一旦WeatherData有了新数据,这些布告板需马上更新;
- 此系统必须可扩展,让其他开发人员创建定制的布告板
错误示范
class WeatherData {
public:
void measurementsChanged() {
float temp = getTemperature();
float humidity = getHumidity();
float pressure = getPressure();
currentConditionsDisplay->update(temp, humidity, pressure);
statisticsDisplay->update(temp, humidity, pressure);
forecastDisplay->update(temp, humidity, pressure);
}
}
上述代码包含以下几点问题:
update()的参数列表完全相同,可考虑将其实现为一个统一的接口;- 将来布告板可能会增多,需要改动
WeatherData对象; - 布告板对象与
WeatherData严重耦合。
观察者模式
观察者模式定义了对象之间的一对多依赖,当一个对象状态改变时,他的所有依赖者都会收到通知并且自动更新。
一个栗子
我们看看报纸与杂志的订阅是怎么回事:
- 报社的业务是出版报纸;
- 用户向某家报社订阅报纸,只要报社有新报纸出版,就会给用户送过去。只要该用户一直是报社的订户,那么他会一直收到报纸;
- 当该用户不想要看报纸的时候,取消订阅,报社便不再订报纸;
- 只要报社还在,就会一直有人向其订阅报纸或者取消订阅报纸。
如果了解了报社的订阅是怎么回事,自然也就知道观察者模式是怎么回事,只是名字不太一样:出版者改称为“主题”(Subject),订阅者改称“观察者”(Observer)。
类图
松耦合的威力
当两个对象之间松耦合,它们依然可以交互,但是不太清楚彼此细节。观察者模式提供了一种对象设计,让主题和观察者之间松耦合。
在任何时候,我们都可以新增新的观察者而无需修改主题,主题唯一依赖的就是一个实现了Observer接口的对象列表。
同时,我们可以独立的复用主题或观察者,因为二者并不紧耦合。改变主题或者观察者的任意一方,并不会影响另一方。
设计原则
为了交互对象之间的松耦合设计而努力。
松耦合的设计之所以让我们建立有弹性的OO系统,能够应对变化,是因为对象之间的依赖降到了最低。
接口设计
class Subject{
public:
virtual void regiterObserver(Observer* o) = 0;
virtual void removeObserver(Observer* o) = 0;
virtual void notifyObservers() = 0;
};
class Observer{
public:
virtual void update(float temp, float humidity, float pressure) = 0;
};
class DisplayElement{
public:
virtual void display() = 0;
}
Subject
class WeatherData : public Subject{
public:
WeatherData(){
observers = {};
}
void registerObserver(Observer* o){
observers.add(o);
}
void removeObserver(Observer* o){
observers.erase(o);
}
void notifyObservers(){
for (auto o : observers){
o->update();
}
}
void measurementChanged(){
notifyObservers();
}
private:
std::set<Observer*> observers;
float temperature;
float humidity;
float pressure;
};
Observer
class CurrentConditionDisplay : public Observer, public DisplayElement {
public:
CurentConditionDisplay(Subject* weatherData) {
this->weatherData = weatherData;
this->weatherData->registerObserver(this);
}
void update(float temperature, float humidity, float pressure) {
this->temperature = temperature;
this->humidity = humidity;
display();
}
void display(){
std::cout << "Current conditions: " << temperature << " degrees and "
<< humidity + < "% humidity";
}
private:
float temperature;
float humidity;
Subject* weatherData;
}
观察者模式的另一种实现
在上述实现中,observer是无法控制接收消息的频率的,只能被动地接收来自subject的信息。
参考书目
[1] Head First Design Patterns: Eric Freeman, Elisabeth Freeman, Bert Bates, Kathy Sierra