C/C++设计模式之观察者模式

背景

通过一个例子,一步步演变出一个设计模式。

气象站发布气象资料给数据中心,数据中心经过处理,将气象信息更新到多个不同的显示终端(A 和B等等)。

获取数据
显示
显示
显示
data center
气象站
温度感应装置
湿度感应装置
气压感应装置
显示装置1
显示装置2
显示装置...

此系统包含三个部分:气象站(获取实际气象数据的物理装置)、数据中心(追踪来自气象站的数据,并更新显示装置)、显示装置(显示目前天气状况给用户看)。

如果要实现这个项目,需要建立一个应用,利用数据中心对象取得数据,并更新显示装置。因此我们首先想到这个一个类模型:

DataCenter
getTemperature()
getHumidity()
getPressure()
measurtementsChanged()
other(...)

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号
..,.
主题对象
未订阅的对象 1
未订阅的对象 2

(1)主题对象管理着某些数据。
(2)当主题内的数据改变了,就会通知观察者;新的数据会以某种形式送到观察者手里。
(3)观察者已经订阅(注册)主题以便在主题数据改变时能够收到更新。
(4)未订阅的对象不是观察者,所以主题数据改变时不会被通知。

观察者模式的执行过程:

添加到订阅名单
有新数据
取消订阅
移除
有新数据
06. 主题又有新数据,所有观察者都得到通知,2号已经不是观察者不会被通知
观察者对象集合
通知数据更新了
通知数据更新了
通知数据更新了
主题对象
1号
3号
对象A
2号
05. 主题将2号移除观察者名单
主题对象
2号
观察者对象集合
1号
3号
对象A
04. 2号不想做观察者了,请求除名
观察者对象集合
取消订阅
主题对象
2号
1号
3号
对象A
03. 主题有了新数据,所有观察者都收到通知
观察者对象集合
通知数据更新了
通知数据更新了
通知数据更新了
通知数据更新了
主题对象
1号
2号
3号
对象A
02. 对象A成为了观察者,静候通知
主题对象
观察者对象集合
1号
2号
3号
对象A
01. 对象A请求订阅,要做一个观察者
要订阅
主题对象
对象A
观察者对象集合
1号
2号
3号

定义观察者模式

勾勒观察者模式时,可以利用报纸订阅服务,以及出版者和订阅者比拟这一切。

定义

观察者模式定义了对象之间一对多依赖,这样一来,当一个对象改变状态时,它的所有依赖者都会收到通知并自动更新。

主题有了新数据,所有观察者都收到通知
依赖者对象
自动更新或通知
自动更新或通知
自动更新或通知
自动更新或通知
主题对象
1号
2号
3号
...

主题和观察者定义了一对多的关系。观察者依赖主题,只要主题状态一有变化,观察者就会被通知。根据通知的风格,观察者可能因此新值而更新。

实现观察者模式的方法不止一种,但是,以包含subject与observer接口的类设计的做法最常见。

类图

许多观察者
«interface»
Subject
registerObserver()
removeObserver()
notifyObserver()
«interface»
Observer
update()
ConcreteSubject
setState
registerObserver()
removeObserver()
notifyObserver()
getState()
ConcreteObserver
update()
other()

(1)主题接口,对象使用此接口注册为观察者,或者把自己从观察者中移除。
(2)每个主题可以有许多观察者。
(3)所有潜在的观察者必须实现观察者接口,这个接口只有update()方法,当主题状态改变时它被调用。
(4)一个具体的主题总是实现主题接口,除了注册和撤销方法之外,具体主题还实现了notifyObserver()方法,此方法用于在状态改变时更新所有当前观察者。
(5)具体的主题也可能有设置和获取状态的方法。
(6)具体的观察者可以是实现ConcreteObserver接口的任意类。观察者必须注册具体主题,以便接收更新。

松耦合

当两个对象之间松耦合,它们依然可以交互,但不清楚彼此的细节。
观察者模式提供了一种对象设计,让主题和观察者之间松耦合。
(1)关于观察者的一切,主题只知道观察者实现了某个接口(Observer接口)。主题不需要知道观察者具体是谁、做了什么等。
(2)任何时候都可以增加新的观察者。因为主题唯一依赖的东西是一个实现Observer接口的对象列表,所以可以随时增加观察者。事实上,运行时可以用新的观察者取代现有的观察者,主题不会受到任何影响;同样,也可以在任何时候移除某些观察者。
(3)有新类型的观察者出现时,主题代码不需要改变。假如有新的具体类需要当观察者,不需要为了兼容新类型而修改主题代码,所有要做的就是在新类中实现观察者接口,然后注册为观察者即可;主题只会发送通知给所有实现观察者接口的对象。
(4)可以独立的复用主题或观察者。如果在其他地方需要使用主题或观察者,可以轻易的复用,因为两者并非紧耦合。
(5)改变主题或观察者其中一方,不会影响另一方。因为两者是松耦合的,所以只要它们之间的接口任然被遵守,就可以自由的改变它们

松耦合的设计之所以能让我们建立弹性的OO系统,能够应对变化,是因为对象之间的互相依赖降到了最低。

设计气象站

观察者
主题
«interface»
Subject
registerObserver()
removeObserver()
notifyObserver()
«interface»
Observer
update()
«interface»
DisplayElement
display()
WeatherData
setState
registerObserver()
removeObserver()
notifyObserver()
getState()
getTemperature()
getHumidity()
getPressure()
measurtementsChanged()
other(...)
CurrentConditionsDisplay
update()
display(/*显示当前观测值*/)
StatisticsDisplay
update()
display(/*显示最小、平均和最大观测值*/)
ForecastDisplay
update()
display(/*显示天气预报*/)

(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;
}

总结

观察者模式有很多的实现方式,示例只是简单的一种,但是,麻雀虽小五脏俱全。
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Lion Long

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值