背景
通过一个例子,一步步演变出一个设计模式。
气象站发布气象资料给数据中心,数据中心经过处理,将气象信息更新到多个不同的显示终端(A 和B等等)。
此系统包含三个部分:气象站(获取实际气象数据的物理装置)、数据中心(追踪来自气象站的数据,并更新显示装置)、显示装置(显示目前天气状况给用户看)。
如果要实现这个项目,需要建立一个应用,利用数据中心对象取得数据,并更新显示装置。因此我们首先想到这个一个类模型:
getTemperature()、getHumidity()、getPressure()三个方法获取气象测试数据。
一旦气象测试数据准备妥当要更新时,measurtementsChanged()会被调用;不在乎是如何调用的,只在乎它被调用。
主要工作是实现measurtementsChanged(),好让它更新目前气象数据到显示装置。
一个错误示范
按照上述模型,我们可能想到在measurtementsChanged()中添加推送代码。
class DataCenter{
public:
// 实例变量声明
void measurtementsChanged()
{
float temp=getTemperature();
float humidity=getHumidity();
float pressure=getPressure();
// 更新显示终端
DisplayA->update(temp,humidity,pressure);
DisplayB->update(temp,humidity,pressure);
DisplayC->update(temp,humidity,pressure);
// ...
}
// 其他方法
}
这有什么问题呢?针对具体实现编程,会导致我们以后在增加或删除显示装置时必须修改程序。
怎么改进呢?可以看到,更新显示终端的接口像是一个统一接口,update的参数都是一样的;可以试试将改变的地方封装起来。
认识观察者模式
先了解报纸和杂志的订阅流程。
(1)报社的业务是出版报纸。
(2)向某报社订阅报纸,只要他们有新报纸出版,就会给你送过来。只要你一直是他们的订阅客户,你就会一直收到新报纸。
(3)当不想再看报纸时,取消订阅,他们就不会再送报纸过来。
(4)只要报社还在运营,就会一直有人向他们订阅报纸或取消订阅报纸。
观察者模式就如同上述一样,出版者就是“主题”,订阅者就是“观察者”;即出版者+订阅者=观察者模式。
(1)主题对象管理着某些数据。
(2)当主题内的数据改变了,就会通知观察者;新的数据会以某种形式送到观察者手里。
(3)观察者已经订阅(注册)主题以便在主题数据改变时能够收到更新。
(4)未订阅的对象不是观察者,所以主题数据改变时不会被通知。
观察者模式的执行过程:
定义观察者模式
勾勒观察者模式时,可以利用报纸订阅服务,以及出版者和订阅者比拟这一切。
定义
观察者模式定义了对象之间一对多依赖,这样一来,当一个对象改变状态时,它的所有依赖者都会收到通知并自动更新。
主题和观察者定义了一对多的关系。观察者依赖主题,只要主题状态一有变化,观察者就会被通知。根据通知的风格,观察者可能因此新值而更新。
实现观察者模式的方法不止一种,但是,以包含subject与observer接口的类设计的做法最常见。
类图
(1)主题接口,对象使用此接口注册为观察者,或者把自己从观察者中移除。
(2)每个主题可以有许多观察者。
(3)所有潜在的观察者必须实现观察者接口,这个接口只有update()方法,当主题状态改变时它被调用。
(4)一个具体的主题总是实现主题接口,除了注册和撤销方法之外,具体主题还实现了notifyObserver()方法,此方法用于在状态改变时更新所有当前观察者。
(5)具体的主题也可能有设置和获取状态的方法。
(6)具体的观察者可以是实现ConcreteObserver接口的任意类。观察者必须注册具体主题,以便接收更新。
松耦合
当两个对象之间松耦合,它们依然可以交互,但不清楚彼此的细节。
观察者模式提供了一种对象设计,让主题和观察者之间松耦合。
(1)关于观察者的一切,主题只知道观察者实现了某个接口(Observer接口)。主题不需要知道观察者具体是谁、做了什么等。
(2)任何时候都可以增加新的观察者。因为主题唯一依赖的东西是一个实现Observer接口的对象列表,所以可以随时增加观察者。事实上,运行时可以用新的观察者取代现有的观察者,主题不会受到任何影响;同样,也可以在任何时候移除某些观察者。
(3)有新类型的观察者出现时,主题代码不需要改变。假如有新的具体类需要当观察者,不需要为了兼容新类型而修改主题代码,所有要做的就是在新类中实现观察者接口,然后注册为观察者即可;主题只会发送通知给所有实现观察者接口的对象。
(4)可以独立的复用主题或观察者。如果在其他地方需要使用主题或观察者,可以轻易的复用,因为两者并非紧耦合。
(5)改变主题或观察者其中一方,不会影响另一方。因为两者是松耦合的,所以只要它们之间的接口任然被遵守,就可以自由的改变它们
松耦合的设计之所以能让我们建立弹性的OO系统,能够应对变化,是因为对象之间的互相依赖降到了最低。
设计气象站
(1)Subject是主题接口。
(2)所有的气象组件都实现Observer观察者接口。这样,主题在需要通知观察者时,有了一个共同的接口。
(3)DisplayElement是显示接口,显示装置只需要实现display()方法。
(4)WeatherData实现Subject接口。
(5)CurrentConditionsDisplay显示装置根据WeatherData对象显示当前观测值;StatisticsDisplay跟踪显示最小、平均和最大观测值值等。
实现气象站
实现接口类
class Observer {
public:
virtual void update(float temp, float humidity, float pressure) {
// 气象观测值改变时,将相关值作为参数传给观察者。
// 所有的观察者都必须实现此方法,以实现观察者接口。
}
};
class Subject {
public:
virtual void registerObserver(Observer o) {
// 需要观察者作为变量,用来注册
}
virtual void removeObserver(Observer o) {
// 需要观察者作为变量,用来移除
}
virtual void notifyObservers() {
// 主题状态变化调用此方法,通知所有观察者
}
};
class DisplayElement {
public:
virtual void display() {
// 显示装置需要显示时调用此方法。
}
};
思考:把观察值直接传给观察者是否合理呢?这些观测值未来会改变吗?如果以后会改变,这些变化是否被很好的封装?或者是需要修改许多代码才能办到?
实现主题接口
// 实现Subject的接口
class WeatherData : public Subject {
private:
std::list<Observer*> obs;// 记录观察者
float temperature=0;
float humidity=0;
float pressure=0;
public:
void registerObserver(Observer* o)
{
obs.emplace_back(o);
cout << "add observer";
cout << o << endl;
}
void removeObserver(Observer* o)
{
obs.remove(o);
}
void notifyObservers()
{
// 把状态告诉所有的观察者
for (auto iter : obs)
{
cout << "notifyObservers";
cout << iter << endl;
iter->update(temperature, humidity, pressure);
}
}
void measurtementsChanged()
{
notifyObservers();
}
void setMeasurtements(float temp, float humidity, float pressure)
{
this->temperature = temp;
this->humidity = humidity;
this->pressure = pressure;
measurtementsChanged();
}
};
建立显示装置
显示装置 实现Observer的接口,可以从WeatherData对象中获得改变。
显示装置 实现DisplayElement的接口。
// 显示装置 实现Observer的接口,可以从WeatherData对象中获得改变
// 显示装置 实现DisplayElement的接口
class CurrentConditionsDisplay :public Observer, public DisplayElement {
private:
float temperature = 0;
float humidity = 0;
float pressure = 0;
public:
// 把数据保存下来,用于显示
void update(float temp, float humidity, float pressure)
{
this->temperature = temp;
this->humidity = humidity;
this->pressure = pressure;
display();
}
// 显示气象数据
void display()
{
cout << "Current conditions: " << endl;
cout << temperature;
cout << " F degrees and ";
cout << humidity;
cout << " % humidity and ";
cout << pressure;
cout << " humidity" << endl;
}
};
class StatisticsDisplay :public Observer, public DisplayElement {
private:
float temperature = 0;
float humidity = 0;
float pressure = 0;
int count = 0;
public:
// 把数据保存下来,用于显示
void update(float temp, float humidity, float pressure)
{
count++;
this->temperature += temp;
this->humidity += humidity;
this->pressure += pressure;
display();
}
// 显示气象数据
void display()
{
cout << "Current conditions average value: " << endl;
cout << temperature / count;
cout << " F degrees and ";
cout << humidity / count;
cout << " % humidity and ";
cout << pressure / count;
cout << " humidity" << endl;
}
};
完整示例代码
测试我们的观察者模式。
#include <iostream>
#include <list>
using namespace std;
class Observer {
public:
virtual void update(float temp, float humidity, float pressure) {
// 气象观测值改变时,将相关值作为参数传给观察者。
// 所有的观察者都必须实现此方法,以实现观察者接口。
}
};
class Subject {
public:
virtual void registerObserver(Observer o) {
// 需要观察者作为变量,用来注册
}
virtual void removeObserver(Observer o) {
// 需要观察者作为变量,用来移除
}
virtual void notifyObservers() {
// 主题状态变化调用此方法,通知所有观察者
}
};
class DisplayElement {
public:
virtual void display() {
// 显示装置需要显示时调用此方法。
}
};
// 实现Subject的接口
class WeatherData : public Subject {
private:
std::list<Observer*> obs;// 记录观察者
float temperature=0;
float humidity=0;
float pressure=0;
public:
void registerObserver(Observer* o)
{
obs.emplace_back(o);
cout << "add observer";
cout << o << endl;
}
void removeObserver(Observer* o)
{
obs.remove(o);
}
void notifyObservers()
{
for (auto iter : obs)
{
cout << "notifyObservers";
cout << iter << endl;
iter->update(temperature, humidity, pressure);
}
}
void measurtementsChanged()
{
notifyObservers();
}
void setMeasurtements(float temp, float humidity, float pressure)
{
this->temperature = temp;
this->humidity = humidity;
this->pressure = pressure;
measurtementsChanged();
}
};
// 显示装置 实现Observer的接口,可以从WeatherData对象中获得改变
// 显示装置 实现DisplayElement的接口
class CurrentConditionsDisplay :public Observer, public DisplayElement {
private:
float temperature = 0;
float humidity = 0;
float pressure = 0;
public:
// 把数据保存下来,用于显示
void update(float temp, float humidity, float pressure)
{
this->temperature = temp;
this->humidity = humidity;
this->pressure = pressure;
display();
}
// 显示气象数据
void display()
{
cout << "Current conditions: " << endl;
cout << temperature;
cout << " F degrees and ";
cout << humidity;
cout << " % humidity and ";
cout << pressure;
cout << " humidity" << endl;
}
};
class StatisticsDisplay :public Observer, public DisplayElement {
private:
float temperature = 0;
float humidity = 0;
float pressure = 0;
int count = 0;
public:
// 把数据保存下来,用于显示
void update(float temp, float humidity, float pressure)
{
count++;
this->temperature += temp;
this->humidity += humidity;
this->pressure += pressure;
display();
}
// 显示气象数据
void display()
{
cout << "Current conditions average value: " << endl;
cout << temperature / count;
cout << " F degrees and ";
cout << humidity / count;
cout << " % humidity and ";
cout << pressure / count;
cout << " humidity" << endl;
}
};
/**********************测试**********************/
int main()
{
WeatherData wd;
Observer *ccd = new CurrentConditionsDisplay();
Observer *sd = new StatisticsDisplay();
wd.registerObserver(ccd);
wd.registerObserver(sd);
wd.setMeasurtements(80, 65, 30.5f);
wd.setMeasurtements(88, 78, 27.5f);
wd.removeObserver(ccd);
wd.setMeasurtements(72, 95, 8.9f);
delete sd;
delete ccd;
return 0;
}
总结
观察者模式有很多的实现方式,示例只是简单的一种,但是,麻雀虽小五脏俱全。