C++|设计模式(七)|⭐️观察者模式与发布/订阅模式,你分得清楚吗

news2024/11/15 23:32:02

本文内容来源于B站:
【「观察者模式」与「发布/订阅模式」,你分得清楚吗?】

文章目录

  • 观察者模式(Observer Pattern)的代码优化
  • 观察者模式 与 发布订阅模式 他们是一样的吗?
    • 发布订阅模式
    • 总结

我们想象这样一个场景:
我们需要开发一个软件系统,该系统可以实时接收某支股票的最新价格,系统可以控制,将这个价格展示在路边的电子广告牌上,同时,坐在控制室中的管理人员可以在显示器上看到这个价格。
当每支股票价格发生变化时,电子广告牌和显示器上的股票价格也要实时变化。

我们首先使用C++来模拟他的一种可能的实现方式:

  • 引入必要的头文件;
  • 创建一个 Monitor 类来表示显示器,其中包含 print 方法来更新显示器的显示内容;
  • 创建一个 Billboard 类来表示广告牌,同样已有 print 方法来进行更新;
  • 创建 Stock 类,该类封装了与股票相关的数据和操作,它包含有一个表示当前股票价格的成员变量 price,以及一个用于更改其值的成员方法 setPrice。
  • 既然 Stock 类是通过 setPrice 来更新股票价格的,那么我们也可以在这里调用 Monitor 和 Billboard 类中的接口来同步更新广告牌和显示器上的数据,所以我们在 Stock 类找那个加入这两个类中的指针,来实现接口的调用。
  • 需要补充的是,我们需要在 Stock 类中初始化这里的两个类指针,让他们指向已经创建好的 Monitor 和 Billboard 类对象。
#include <iostream>
struct Monitor {
	void print(int v) {
		std::cout << "Monitor:" << v;
	}
};
struct Billboard {
	void display(int v) {
		std::cout << "Billboard:" << v;
	}
};
struct Stock {
	int price = 20;
	Monitor* monitor;
	Billboard* billboard;
	Stock(Monitor* monitor, Billboard* billboard)
		: monitor(monitor), billboard(billboard)
	void setPrice(int v) {
		price = v;
		monitor->print(price);
		billboard->print(price);
	}
};

随后我们就可以调用 Stock 对象上的 setPrice 方法来更新股票价格,并且广告牌与显示器上的数字也会被相应更新。

int main () {
	Monitor monitor;
	Billboard billboard;
	Stock stock {&monitor, &billboard};
	stock.setPrice(10);
}

但是这样的实现方式有许多的问题:

  1. 类之间的紧耦合:Stock 类的稳定性依赖于 Monitor 与 Billboard 类接口的稳定,而当这些接口的名称或使用方式发生变化时,那么 Stock 类就需要被同时修改;并且同样的情况也发生在有更多的显示媒介加入时,比如软件希望同时支持在手机APP上显示最新的股票价格,这个时候我们也需要再次修改 Stock 类。
  2. 这样的紧耦合使得代码的维护成本变得很高,那么如何解决呢?答案就是观察者模式!

观察者模式(Observer Pattern)的代码优化

也就是我们的 Monitor 类和 Billboard 类都是观察者,这里我们可以提供一个统一的父类 Observer ,并且在里面写入 update 接口,这个接口将所有观察者在观测时发生的不同动作进行了统一的包装。Observer 的构造和析构会在后面进行补全:

#include <iostream>
struct Observer {
	Observer();
	virtual ~Observer();
	virtual void update(int) = 0;
}
struct Monitor {
	void print(int v) {
		std::cout << "Monitor:" << v;
	}
};
struct Billboard {
	void display(int v) {
		std::cout << "Billboard:" << v;
	}
};

然后我们的 Monitor 类的构造函数也做相应的修改,然后我们在 Monitor 类中实现父类的 update 接口,Billboard 观察者类也同理,所有观察者都需要将当观测数据发生变化时,可能发生的行为封装在统一的 update(int) 接口中。

struct Monitor {
	Monitor() : Observer() {}
	void print(int v) {
		std::cout << "Monitor:" << v;
	}
	void update(int v) override {
		print(v);
	}
};
struct Billboard {
	Billboard() : Observer() {}
	void display(int v) {
		std::cout << "Billboard:" << v;
	}
	void update(int v) override {
		display(v);
	}
};

下面我们把 Stock 类也加入到代码中,我们首先为他提供一个前置声明。接着我们在 Observer 类中加入一个指向 Stock 类对象的指针引用,随后我们改写 Monitor 和 Billboard 类的构造函数,接着我们将之前那段 Stock 类的定义原封不动得放入到代码中。

#include <iostream>

struct Stock;

struct Observer {
	Stock* stock;
	Observer(Stock* stock);
	virtual ~Observer();
	virtual void update(int) = 0;
};
struct Monitor {
	Monitor(Stock* stock) : Observer(stock) {}
	void print(int v) {
		std::cout << "Monitor:" << v;
	}
};
struct Billboard {
	Billboard(Stock* stock) : Observer(stock) {}
	void display(int v) {
		std::cout << "Billboard:" << v;
	}
};

struct Stock {
	int price = 0;
	void setPrice(int v) {
		price = v;
	}
};

在观察者模式下, Stock 类作为所有观察者关注的对象,它需要支持三个成员方法,notify(int) detach(Observer*) attach(Observer*),这三个方法会围绕 Stock 类内部维护的一个集合进行操作。这个集合中就存放有指向 Oberser 子类对象的指针。这些指针指向的就是所有对 Stock 类感兴趣的观察者对象。

#include list
struct Stock {
	int price = 0;
	std::list<Observer*> observerList;
	void attach(Observer* o) {
		observerList.push_back(0);
	}
	void detach(Observer* o) {
		observerList.remove(0);
	}
	void setPrice(int v) {
		price = v;
	}
};

这里我们的 notify(int v) 方法是观察者模式的核心,这个方法会遍历观察者指针容器,并以此调用每个观察者对象上的 update 方法。通过这种方式,Stock 类便能够在状态发生变化时及时通知所有对此感兴趣的观察者对象来进行相应的更新操作,最后我们在 setPrice 方法中调用 notify 方法,这样在股价发生变化时, 能够通知到所有观察者对象。

#include list
struct Stock {
	int price = 0;
	std::list<Observer*> observerList;
	void attach(Observer* o) {
		observerList.push_back(0);
	}
	void detach(Observer* o) {
		observerList.remove(0);
	}
	void notify(int v) {
		for (auto observer : observerList) {
			observer->update(v);
		}
	}
	void setPrice(int v) {
		price = v;
		notify(v);
	}
};

下一步我们需要补全 Observer 类的构造函数,让每一个观察者对象在完成构造后,都能被加入到某个 Stock 对象的观察者集合中。而当观察者对象被析构时则从相应的观察者集合中被移除。

Observer::observer(Stock* stk) : stock(stk) {
	stock->attach(this);
}
Observer::observer(Stock* stk) : stock(stk) {
	stock->detach(this);
}

最后在 main 函数中我们可以这样来使用这些类:

int main () {
	Stock stock;
	Monitor monitor { &stock };
	Billboard board { &stock };
	stock.setPrice(10);
}

我们可以看到当 Stock 类在构造时,不再依赖任何观察者对象,因此它的实现可以保持稳定。而观察者对象也可以自由选择观察目标,可以是不同的 Stock 对象,同时不同种类的观察者对象的横向扩展也变得更加灵活

UML类图如上,并且在某些情况下我们可以进一步对观察者模式进行抽象。从而得到另一个版本的 UML 类图和另一个版本的代码实现。

我们为 Stock 类提供了独立的抽象接口 Subject ,从而将 attach、detach 以及 notify 这三个标准接口抽离出来。而 Stock 则保有自己的内部状态,为了保证状态访问的合法性,他们仅能够通过专有的访问器进行访问,下面是对应的 C++ 代码实现:

#include <iostream>
#include <list>

struct Observer;
struct Subject {
  std::list<Observer*> observerList;
  virtual void attach(Observer* o) = 0;
  virtual void detach(Observer* o) = 0;
  virtual void notify() = 0;
};

class Stock : public Subject {
  int price = 0;

 public:
  int getPrice();
  void setPrice(int);
  void attach(Observer* o) override;
  void detach(Observer* o) override;
  void notify() override;
};

struct Observer {
  Subject* sub;
  Observer(Subject* sub);
  virtual ~Observer();
  virtual void update() = 0;
};

struct Monitor : Observer {
  Monitor(Subject* sub) : Observer(sub) {}
  void print(int v) const { std::cout << "Monitor: " << v << std::endl; }
  void update() override { print(static_cast<Stock*>(sub)->getPrice()); }
};

struct Billboard : Observer {
  Billboard(Stock* stock) : Observer(stock) {}
  void display(int v) const { std::cout << "Billboard: " << v << std::endl; }
  void update() override { display(static_cast<Stock*>(sub)->getPrice()); }
};

int Stock::getPrice(void) { return price; }
void Stock::setPrice(int v) {
  price = v;
  notify();
}
void Stock::attach(Observer* o) { observerList.push_back(o); }
void Stock::detach(Observer* o) { observerList.remove(0); }
void Stock::notify() {
  for (auto observer : observerList) {
    observer->update();
  }
}

Observer::Observer(Subject* sub) : sub(sub) { sub->attach(this); }

Observer::~Observer() { sub->detach(this); }

int main() {
  Stock stock;
  Monitor monitor{&stock};
  Billboard board{&stock};
  stock.setPrice(10);
}

观察者模式 与 发布订阅模式 他们是一样的吗?

发布订阅模式

发布订阅模式是一种常用的系统架构模式,他用来规定一个系统中的不同部分之间应该如何进行消息传递。

在这个模式中,发布者可以是系统中的消息服务、事件服务;而订阅者可以是系统中的其他服务,比如网关服务、路由服务等等。

发布者与订阅者之间互相不知道对方的存在,他们之间通过名为消息代理的部分连接起来。发布者会向消息代理发送不同类型的消息,比如一个系统通知、一个发生的事件。而订阅者则会与消息代理通信,选择自己想要订阅的消息类型。

在之后的流程中,每当消息代理接收到符合要求的消息,便会把他们直接转发给相应的订阅者进行处理;这便是发布订阅模式的基本形式。

总结

所以综合来看,我们可以得出这样的结论:

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1967235.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

深度学习 —— 个人学习笔记8(层和块、参数管理、自定义层及读写文件)

声明 本文章为个人学习使用&#xff0c;版面观感若有不适请谅解&#xff0c;文中知识仅代表个人观点&#xff0c;若出现错误&#xff0c;欢迎各位批评指正。 十五、层和块 nn.Sequential()   nn.Sequential() 是一个序列容器&#xff0c;用于搭建神经网络的模块按照被传入构…

6-1 从全连接层到卷积

我们之前讨论的多层感知机十分适合处理表格数据&#xff0c;其中行对应样本&#xff0c;列对应特征。 对于表格数据&#xff0c;我们寻找的模式可能涉及特征之间的交互&#xff0c;但是我们不能预先假设任何与特征交互相关的先验结构。 此时&#xff0c;多层感知机可能是最好的…

程序员面试题------N皇后问题算法实现

N皇后问题是一个著名的计算机科学问题&#xff0c;它要求在NN的棋盘上放置N个皇后&#xff0c;使得它们之间不能相互攻击&#xff0c;即任意两个皇后都不能处于同一行、同一列或同一斜线上。这个问题可以看作是一个回溯算法问题&#xff0c;通过逐步尝试不同的放置位置&#xf…

手持气象设备:掌握天气的便捷伙伴

在这个快速变化的时代&#xff0c;手持气象设备成为了我们日常生活中重要的小帮手。它小巧轻便&#xff0c;易于携带&#xff0c;让我们随时随地都能掌握天气变化&#xff0c;为出行、户外活动提供准确参考。 手持气象设备内置了高精度传感器&#xff0c;能够迅速感知并显示当前…

PCB学习

教你怎么检查电路原理图_原理图检视主要内容-CSDN博客https://blog.csdn.net/chenhuanqiangnihao/article/details/113664734

继全球蓝屏后,微软 Azure 云服务因安全错误导致全球宕机

7月30日&#xff0c;微软Azure云服务全球宕机约8小时。该事件由一次DDoS攻击引起&#xff0c;成功触发系统保护机制&#xff0c;但这些防御机制中的实施错误反而进一步放大了影响&#xff0c;最终造成一次大宕机事件。据英国广播公司报道&#xff0c;此次中断持续了大约 10 个小…

5步教你学会古诗词生成AI绘画

本文由 ChatMoney团队出品 首先&#xff0c;打开时下最热门的两个AI工具&#xff0c;mj和chatgpt这两个都是我们在创作AI古诗词绘画中一定要用到的&#xff0c;这里我用的是chatmoneyAI系统 第一步&#xff1a;我们要先使用ChatGPT来生成我们所想要展示古诗的关键词。那么我们…

代码随想录算法训练营第二十一天| 39. 组合总和, 40.组合总和II, 131.分割回文串

今天是回溯算法学习的第二天&#xff0c;主要的学习内容包括&#xff1a;1.组合问题的重复使用 2.组合问题的去重 3.分割问题的处理方法。 39. 组合总和 题目链接&#xff1a;39. 组合总和 - 力扣&#xff08;LeetCode&#xff09; 这个组合问题的特点是&#xff0c;集合内的…

Java:基于TextRank算法的自动摘要(自动生成事件摘要)

TextRank 是一种用于文本摘要的自然语言处理算法。它的工作原理类似于 Google 搜索引擎的 PageRank 算法&#xff0c;即根据文本中每个单词出现的频率和被引用的次数来评估它的重要性。 所谓自动摘要&#xff0c;就是从文章中自动抽取关键句。何谓关键句&#xff1f;人类的理解…

最好用的复制粘贴软件pastemate功能简介

这应当是windows下最好用的复制粘贴软件&#xff0c;遥遥领先的复制粘贴软件。 效增PasteMate - 下载页面 windows下界面最优美&#xff0c;操作最方便的复制粘贴神器&#xff0c;学生党论文必备&#xff0c;效率神器 pastemate 1.搜索功能&#xff0c;能够按文本、图片、文件…

C# 构建观测者模式(或者为订阅者模型)

前言&#xff1a; 观测者模型的基本理念&#xff0c;就是&#xff0c;我有一个公共的事件&#xff0c;定义好他的事件的触发、数据接口。然后&#xff0c;通过增加订阅者&#xff08;实例&#xff09;来订阅这个事件的&#xff0c;或者说观察这个事件。如果事件发生&#xff0…

软件测试的挑战和压力

软件测试过程中可能会遇到很多挑战&#xff0c;比如&#xff1a; 1. 需求不明确或不稳定。如果需求文档不完整、不清晰或不一致&#xff0c;或者需求在开发过程中频繁变更&#xff0c;那么测试人员就很难设计和执行有效的测试用例&#xff0c;也很难判断测试结果是否符合预期。…

5年经验的软件测试人员,碰到这样的面试题居然会心虚......

我们这边最近的面试机会比较多&#xff0c;但是根据他们的反馈&#xff0c;结束后大部分都没音信了&#xff0c;因为现在企业面试问的非常多&#xff0c;范围非常广&#xff0c;而且开放性的问题很多&#xff0c;很多人即便面试前刷了成百上千道面试题&#xff0c;也很难碰到一…

C语言——指针数组

文章目录 &#x1f34a;自我介绍&#x1f34a;前言&#x1f34a;含义&#x1f34a;输出指针数组中的值&#x1f34a;指针数组工程的用法&#xff08;模拟linux的内核代码&#xff09; 你的点赞评论就是对博主最大的鼓励 当然喜欢的小伙伴可以&#xff1a;点赞关注评论收藏&…

实习中学到的一点计算机知识(MP4在企业微信打不开?)

我在实习中&#xff0c;常有同事向我反馈说我在微信发的视频格式打不开。这就导致我还要一帧帧的盯着某一个时刻来截图&#xff0c;今天查了一下资料尝试修改视频后缀来解决视频的播放问题。 在网上下载mp4的格式&#xff0c;在本地都能播放&#xff0c;怎么可能发上企业微信就…

使用CLI脚手架搭建Vue2项目

一、配置前端的环境 1、下载安装Node.js 网址&#xff1a;Node.js 中文网 (nodejs.com.cn) 参考&#xff1a;【简明图文教程】Node.js的下载、安装、环境配置及测试_node下载安装-CSDN博客 推荐安装路径C盘改为D盘 2、配置nodejs及环境变量【安装的时候勾选Add to PATH就不…

[算法]归并排序(C语言实现)

一、归并排序的定义 归并排序&#xff08;Merge sort&#xff09;是建立在归并操作上的一种有效的排序算法。该算法是采用分治法&#xff08;Divide and Conquer&#xff09;的一个非常典型的应用。 二、归并排序的算法原理 归并排序的算法可以用递归法和非递归法来实现…

2024新版python安装教程【附图片】

Python的安装步骤因操作系统而异&#xff0c;但大致可以分为下载、安装和验证三个主要步骤。以下是Windows系统中Python的详细安装步骤&#xff1a; Windows系统 下载Python安装包 访问Python官网&#xff08;https://www.python.org/&#xff09;。点击页面头部的“Download…

【优秀python django系统案例】基于python的医院挂号管理系统,角色包括医生、患者、管理员三种

随着信息技术的迅猛发展&#xff0c;传统的医院挂号管理方式面临着效率低下、排队时间长、信息不对称等诸多问题。这些问题不仅影响患者的就医体验&#xff0c;也加重了医院工作人员的负担。在此背景下&#xff0c;基于Python的医院挂号管理系统应运而生。该系统旨在通过信息化…

OZON饰品产品什么好卖,OZON热销饰品有哪些

在OZON平台上&#xff0c;饰品产品的销售情况受多种因素影响&#xff0c;包括市场需求、季节变化、消费者偏好以及流行趋势等。以下是一些可能热销的OZON饰品产品类别及具体推荐&#xff1a; OZON热销饰品地址&#xff1a;D。DDqbt。COm/74rDTop1 发带套装 Утика Ком…