一、概述:什么是观察者模式
观察者模式(Observer Pattern)是一种行为型设计模式,它定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个主题对象在状态发生变化时,会通知所有观察者对象,使它们能够自动更新自己。
观察者模式包含以下角色:
主题(Subject):被观察的对象,维护一个观察者列表,提供添加、删除和通知观察者的方法。
观察者(Observer):观察主题对象的状态变化,实现 Update 方法来更新自己的状态。
具体主题(ConcreteSubject):继承自抽象主题,实现具体业务逻辑。
具体观察者(ConcreteObserver):继承自抽象观察者,实现具体业务逻辑。
使用观察者模式可以使得系统更加灵活、可扩展,减少各个模块之间的耦合度。
二、问题的提出
1.需求描述
建立一个Internet气象观测站,由WeatherData对象负责追踪目前的天气状况(温度、湿度、气压),这些信息需要显示在如下三种布告板上:1.目前的状况;2.气象统计;3.简单的预报。当天气状况发生变化时,三种布告板必须实时更新。此外,后续还可以随意添加别的布告板。
2.需求分析
整个气象观测系统的组成如下图:

我们的工作就是:建立一个应用,利用WeatherData对象取得数据,并更新三个布告板。
三、开始工作解决问题
1.WeatherDate的内容
首先了解下WeatherData中都有什么:

类定义:
class WeatherData
{
public:
//我们不在乎这些气象变量如何被设置
double getTemperature();
double getHumidity();
double getPressure();
//一旦气象测量数据被更新,此方法会被调用.我们不在乎是怎么实现的
void measurementsChanged()
{
//你的代码加在这里
}
};
再明确下我们的工作:实现measurementsChanged(),好让它更新目前状况、气象统计和天气预告的显示布告板。
2.错误的示范
先看一种解决方式:
class WeatherData
{
public:
//我们不在乎这些气象变量如何被设置
double getTemperature()
{//TODO something
return double();
}
double getHumidity()
{//TODO something
return double();
}
double getPressure()
{//TODO something
return double();
}
//一旦气象测量数据被更新,此方法会被调用.我们不在乎是怎么实现的
void measurementsChanged()
{
//1.获取气象数据
double temp = getTemperature();
double humidity = getHumidity();
double pressure = getPressure();
//2.现在更新公告板
currentConditionDisplay.update(temp,humidity,pressure);
statisticsDisplay.update(temp,humidity,pressure);
forecastDisplay.update(temp,humidity,pressure);
}
//其他方法
};
这种实现有什么问题呢?似乎是实现了功能啊?
回想之前的教材中,面向对象设计中一个原则:针对接口编程而不是针对实现编程。
针对具体实现编程,会导致我们以后在增加或者删除公告板时必须修改程序。
既然需求是一对多,那我们用观察者模式来试试。不过,在试之前,先了解下观察者模式,然后再回过头将其应用到气象观测站程序。
三、详述观察者模式
1.定义观察者模式
我们先看看报纸和杂志的订阅是怎么回事:
(1)报社的业务就是出版报纸。
(2)向某家报社订阅报纸,只要他们有新报纸出版,就会给你送来。只要你是他们的订户,你就会一直收到新报纸。
(3)当你不想再看报纸的时候,取消订阅,他们就不会送新报纸来了。
(4)只要报社还在运营,就会一直有客户订阅他们的报纸,或者取消订阅。
其实上述示例就是一个典型的观察者模式:出版者+订阅者=观察者模式
出版者改成为“主题”(Subject),订阅者改称为“观察者”(Observer)。
所以当你试图勾勒观察者模式时,可以利用报纸订阅服务,以及出版者和订阅者比拟这一切。
你通常会看到观察者模式会被定义成这样:
观察者模式定义了对象之间一对多依赖,这样一来,当一个对象改变状态时,它的所有依赖者都会收到通知并自动更新。

如上图:主题和观察者定义了一对多的关系。观察者依赖于此主题,只要主题状态一有变化,观察者就会被通知。根据通知的风格,观察者可能因为此新值而更新。
2.观察者模式类图
(图片来源于《Head First 设计模式》第一版)

当两个对象之间松耦合,他们依然可以交互,但是不太清楚彼此的细节。
观察者模式提供了一种对象设计,让主题和观察者之间松耦合。
下面来详细解释:
关于观察者的一切,主题只知道观察者实现了某个接口(也就是Observer接口)。主题不需要知道观察者的具体类是谁、做了些什么或者其他任何细节。
任何时候我们都可以增加新的观察者。因为主题唯一依赖的东西是一个实现Observer接口的对象列表,所以我们可以随时增加、删除观察者而主题不会受到任何影响。
有新类型的观察者出现时,主题代码不需要修改,我们所要做的就是在新的类里实现此观察者接口,然后将其注册为观察者即可。主题不在乎别的,它只会发送通知给所有实现了观察者接口的对象。
我们可以独立的复用主题或者观察者。如果我们需要再其他地方使用主题或者观察者,可以轻易的复用,因为二者并非紧耦合。
改变主题或者观察者其中一方,并不会影响另一方。因为两者是松耦合的,所以只要他们之间的接口任被遵守,我们就可以自由地改变他们。
好了,在理解了观察者模式后,我们继续回到设计气象站应用软件的工作中来。
四、设计气象站
1.首先是类图设计

2.接口与类实现
根据上面的类图,我们来实现这个系统。
首先是接口:
//观察者接口
class Observer
{
public:
//输入参数会传递给观察者
virtual void update(double temp,double humidity,double pressure)=0;
};
//主题接口
class Subject
{
public:
//这两个方法都需要一个观察者作为输入,该观察者是用来注册或者删除的
virtual void registerObserver(Observer *o);
virtual void removeObserver(Observer *o);
//当主题状态改变时,这个方法会被调用,已通知所有观察者
virtual void notifyObserver()=0;
};
//此处单独定义一个布告板显示内容的接口
class DisplayElement
{
public:
virtual void display()=0;
};
接下来在weatherData类中实现Subject主题接口:
//WeatherData类
class WeatherData:public Subject
{
private:
std::vector<Observer> observers;
double temperature;
double humidity;
double pressure;
public:
//注册观察者
void registerObserver(Observer &o)
{
observers.push_back(o);
}
//取消注册
void removeObserver(Observer &o)
{
auto itor =std::find(observers.begin(),observers.end(),o);
if (itor!=observers.end())
{
observers.erase(itor);
}
}
//通知所有观察者由新的数据。因为每个观察者都实现了updata(),所以我们知道如何通知他们
void notifyObserver()
{
for (auto &o:observers)
{
o.update(temperature,humidity,pressure);
}
}
//根据需求中提供的信息,一旦气象测量数据被更新,此方法会被自动调用.我们不在乎是怎么实现的
void measurementsChanged()
{
notifyObserver();
}
//模拟气象站获取气象数据
void setMeasurements(double temp,double humidity,double pressure)
{
this->temperature = temp;
this->humidity=humidity;
this->pressure=pressure;
measurementsChanged();
}
//get方法由气象站提供
//我们不在乎这些气象变量如何被设置
double getTemperature();
double getHumidity();
double getPressure();
};
现在是布告板:实现观察者Observer接口
需求中有三个布告板:1.目前的状况;2.气象统计;3.简单的预报。在此以目前天气状况布告板为例:
//当前状况布告板类:观察者Observer接口实现类
class CurrentConditionDisplay:public Observer,public DisplayElement
{
private:
double temperature;
double humidity;
Subject *weatherData;//有一个主题指针
public:
CurrentConditionDisplay(Subject *wData)
{
this->weatherData = wData;
weatherData->registerObserver(this);//在主题中注册观察者
}
//实现Observer接口中虚函数方法:观察者对象更新到主题中的新数据
void update(double temp,double humidity,double pressure)
{
this->temperature=temp;
this->humidity=humidity;
display();
}
//实现DisplauElement接口中显示的方法
void display()
{
std::cout<<"Current conditions:\n";
std::cout<<"temperature:"<<temperature<<"\n";
std::cout<<"humidity:"<<humidity<<std::endl;
}
};
其他两个布告板类似上述。若后续由新布告板需求,也是一样的。可以看到,新增布告板并不会影响主题。
3.测试程序
接下来让我们通过一个测试程序来启动气象站:
//测试程序
int main()
{
//先建立一个WeatherData对象
WeatherData *weatherData = new WeatherData();//也可以用多态 : Subject *weatherData = new WeatherData();
//建立三个布告板
//1.当前状态布告板
//也可以用多态 : Observer *curCodDisplay = new CurrentConditionDisplay(weatherData);
CurrentConditionDisplay *curCodDisplay = new CurrentConditionDisplay(weatherData);
//2.气象统计布告板
//3.简单预告布告板
//模拟气象站获取到气象数据。每当从气象站检测设备获取到新的数据后,就会立即更新到所有的布告板中
weatherData->setMeasurements(80,60,90);
weatherData->setMeasurements(90,69,88);
return 0;
}
可以看到,每个组件都很容易“插拔”。
4.主动推送与主动拉取
上面的示例中,主题(气象站)在有状态变化时,主动将数据推送给了观察者(各个布告板),观察者(各个布告板)被动的接受了这些数据。
也可以让观察者利用主题的get方法来主动拉取数据。有兴趣的小伙伴可以自己根据《Head First 设计模式》给出的java内置的方法研究下哦~
五、观察者模式结语
观察者模式的用途还是挺常见的。比如现在网络自媒体的推送,又比如某些UI界面中各种空间与对应事件的响应,Qt的信号槽机制等等。
以上为观察者模式,希望对大家有帮助。
感谢Header first 设计模式这本书,写的真棒!后面会继续整理。。。
--观察者模式&spm=1001.2101.3001.5002&articleId=148833725&d=1&t=3&u=79a7a0e39de6419897d093b3b259f898)
1329

被折叠的 条评论
为什么被折叠?



