前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >掌握观察者模式:增强代码的灵活性和可维护性

掌握观察者模式:增强代码的灵活性和可维护性

原创
作者头像
Lion Long
发布2024-10-29 21:06:46
1010
发布2024-10-29 21:06:46
举报
文章被收录于专栏:后端开发技术

一、背景

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

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

图片
图片

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

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

图片
图片

getTemperature()、getHumidity()、getPressure()三个方法获取气象测试数据。 一旦气象测试数据准备妥当要更新时,measurtementsChanged()会被调用;不在乎是如何调用的,只在乎它被调用。 主要工作是实现measurtementsChanged(),好让它更新目前气象数据到显示装置。

二、一个错误示范

按照上述模型,我们可能想到在measurtementsChanged()中添加推送代码。

代码语言:javascript
复制
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)未订阅的对象不是观察者,所以主题数据改变时不会被通知。

观察者模式的执行过程:

图片
图片

四、定义观察者模式

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

4.1、定义

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

图片
图片

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

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

4.2、类图

图片
图片

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

4.3、松耦合

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

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

五、设计气象站

图片
图片

(1)Subject是主题接口。 (2)所有的气象组件都实现Observer观察者接口。这样,主题在需要通知观察者时,有了一个共同的接口。 (3)DisplayElement是显示接口,显示装置只需要实现display()方法。 (4)WeatherData实现Subject接口。 (5)CurrentConditionsDisplay显示装置根据WeatherData对象显示当前观测值;StatisticsDisplay跟踪显示最小、平均和最大观测值值等。

六、实现气象站

6.1、实现接口类

代码语言:javascript
复制
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() {
		// 显示装置需要显示时调用此方法。
	}
};

思考:把观察值直接传给观察者是否合理呢?这些观测值未来会改变吗?如果以后会改变,这些变化是否被很好的封装?或者是需要修改许多代码才能办到?

6.2、实现主题接口

代码语言:javascript
复制
// 实现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();
	}
};

6.3、建立显示装置

显示装置 实现Observer的接口,可以从WeatherData对象中获得改变。 显示装置 实现DisplayElement的接口。

代码语言:javascript
复制
// 显示装置 实现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;
	}
};

七、完整示例代码

测试我们的观察者模式。

代码语言:javascript
复制
#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;
}

八、总结

观察者模式有很多的实现方式,示例只是简单的一种,但是,麻雀虽小五脏俱全。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、背景
  • 二、一个错误示范
  • 三、认识观察者模式
  • 四、定义观察者模式
    • 4.1、定义
      • 4.2、类图
        • 4.3、松耦合
        • 五、设计气象站
        • 六、实现气象站
          • 6.1、实现接口类
            • 6.2、实现主题接口
              • 6.3、建立显示装置
              • 七、完整示例代码
              • 八、总结
              领券
              问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档