当前位置: 首页 > news >正文

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

本文内容来源于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);
}

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

发布订阅模式

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

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

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

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

总结

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

相关文章:

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

本文内容来源于B站&#xff1a; 【「观察者模式」与「发布/订阅模式」&#xff0c;你分得清楚吗&#xff1f;】 文章目录 观察者模式&#xff08;Observer Pattern&#xff09;的代码优化观察者模式 与 发布订阅模式 他们是一样的吗&#xff1f;发布订阅模式总结 我们想象这样一…...

计算机毕业设计选题推荐-学院教学工作量统计系统-Java/Python项目实战

✨作者主页&#xff1a;IT毕设梦工厂✨ 个人简介&#xff1a;曾从事计算机专业培训教学&#xff0c;擅长Java、Python、微信小程序、Golang、安卓Android等项目实战。接项目定制开发、代码讲解、答辩教学、文档编写、降重等。 ☑文末获取源码☑ 精彩专栏推荐⬇⬇⬇ Java项目 Py…...

人机交互不仅仅是科技问题

人机交互不仅仅局限于物理和数理科学的应用&#xff0c;还涉及到更广泛的管理、文理、哲学、艺术、伦理以及法律等领域。下面这些领域在人机协同和智能系统应用中扮演着重要角色&#xff1a; 智能系统在企业管理、资源分配、决策支持等方面的应用&#xff0c;可以帮助管理者优化…...

Lua Debug.GetInfo

在 Lua 中&#xff0c;debug.getinfo 函数的第一个参数指定了要获取信息的函数的级别。这个级别是一个整数&#xff0c;表示调用栈的深度。以下是一些常见的级别和它们的含义&#xff1a; - 1&#xff1a;当前函数&#xff08;即调用 debug.getinfo 的函数&#xff09;。 - 2&a…...

每日刷题(最短路、图论)

目录 游戏 思路 代码 魔法 思路 代码 P1364 医院设置 思路 代码 P1144 最短路计数 思路 代码 游戏 I-游戏_河南萌新联赛2024第&#xff08;三&#xff09;场&#xff1a;河南大学 (nowcoder.com) 思路 利用dijkstra去寻找起点到其余所有点的最短路径&#xff0c;当…...

远程服务器训练网络之tensorboard可视化

cd到tensorboard events存储的位置 启动tensorboard tensorboard --logdir./ 得到运行结果&#xff1a; TensorBoard 1.13.1 at http://work:6006 (Press CTRLC to quit) 创建tunnel映射到本地&#xff0c;在本地ssh&#xff0c;最好使用公网地址 ssh -N -L 8080:localhost:60…...

MySQL锁详解

锁是计算机在执行多线程或线程时用于并发访问同一共享资源时的同步机制&#xff0c;MySQL中的锁是在服务器层或者存储引擎层实现的&#xff0c;保证了数据访问的一致性与有效性。 MySQL锁&#xff1a; 按粒度分为&#xff1a;全局锁、表级锁、页级锁、行级锁。按模式分为&…...

面试问题记录:

1&#xff0c;hashmap扩容的时候&#xff0c;链表超长但不满足转变成红黑树的条件时&#xff1a; 【HashMap】链表和红黑树互相转换的几种情况和数组的扩容机制_hashmap红黑树转链表条件-CSDN博客 2&#xff0c;cglib与proxy区别 JDK 动态代理和 CGLIB 动态代理对比_动态代理…...

vue如何在组件中监听路由参数的变化

使用 watch 监听 $route 对象 的变化&#xff0c;从而捕捉路由参数的变化 beforeRouteUpdate 导航守卫 当前组件路由更新时调用 beforeRouteUpdate 钩子只在组件被复用时调用&#xff0c;即当组件实例仍然存在时。如果组件是完全重新创建的&#xff0c;那么应该使用 beforeR…...

antd中form表单校验文件上传

antd中文件上传需要单独设置this.model中得数据 this.$set(this.model, filePath,上传成功后返回得文件路径地址)...

商家转账到零钱2024最新开通必过攻略

微信支付商家转账到零钱功能申请设置了人工审核的门槛&#xff0c;本意是为了防止没有合规使用场景的商户滥用该功能&#xff0c;但这也让相当多的真实用户被一次次拒之门外。结合过去6年开通此类产品的经验&#xff0c;今天我们就以2024年最新的的商家转账到零钱的开通流程做一…...

2024全新Thinkphp聊天室H5实时聊天室群聊聊天室自动分配账户完群组/私聊/禁言等功能/全开源运营版本

全开源运营版本聊天室H5实时聊天室群聊聊天室自动分配账户完群组/私聊/禁言等功能 运营版本的聊天室&#xff0c;可以添加好友&#xff0c;建立群组&#xff0c;私聊&#xff0c;禁言功能 H5TP5.0mysqlPHP 源码开源不加密...

(一)javascript中class类

在 JavaScript 中使用 class 语法可以定义类的结构&#xff0c;其中可以包括静态属性/方法、私有属性/方法、公共属性/方法和受保护属性/方法。这些概念有助于封装和数据隐藏&#xff0c;使得代码更加模块化和安全。下面我会解释这些不同的属性和方法&#xff0c;以及如何在类中…...

【注意力MHA,MQA,GQA,MLA】

注意力机制优化简明图解 1. 多头注意力&#xff08;MHA&#xff09; 图示&#xff1a; Input --> [Attention Head 1]--> [Attention Head 2]--> [Attention Head 3]--> ...--> [Attention Head N]--> [Concatenate] --> Output公式&#xff1a; Outpu…...

《从零开始做个摸鱼小网站! · 序》灵感来源

序 大家好呀&#xff0c;我是summo&#xff0c;这次来写写我在上班空闲(摸鱼)的时候做的一个小网站的事。去年阿里云不是推出了个活动嘛&#xff0c;2核2G的云服务器一年只要99块钱&#xff0c;懂行的人应该知道这个价格在业界已经是非常良心了&#xff0c;虽然优惠只有一年&a…...

计算机基础(Windows 10+Office 2016)教程 —— 第5章 文档编辑软件Word 2016(上)

文档编辑软件Word 2016 5.1 Word 2016入门5.1.1 Word 2016 简介5.1.2 Word 2016 的启动5.1.3 Word 2016 的窗口组成5.1.4 Word 2016 的视图方式5.1.5 Word 2016 的文档操作5.1.6 Word 2016 的退出 5.2 Word 2016的文本编辑5.2.1 输入文本5.2.3 插入与删除文本5.2.4 复制与移动文…...

短视频矩阵管理系统源码:实现短视频内容全面布局

随着移动互联网的普及&#xff0c;短视频应用逐渐成为人们获取信息、娱乐休闲的重要途径。为了满足用户多样化需求&#xff0c;实现短视频内容的全面布局&#xff0c;短视频矩阵管理系统应运而生。本文将详细介绍短视频矩阵管理系统的源码实现&#xff0c;帮助您更好地理解并应…...

系统设计中15 个最重要的权衡

系统设计的第一法则&#xff1a;一切都与权衡有关。 在设计系统时&#xff0c;我们需要决定要包含哪些功能以及要忽略哪些功能。每次我们做这个决定时&#xff0c;我们都在进行权衡。在本文中&#xff0c;我们将探讨系统设计中遇到的15个最常见的权衡问题&#xff0c;并使用实…...

12年外贸实战经验,一定对你有帮助!

更多外贸干货及开发客户的方法&#xff0c;尽在微信【千千外贸干货】 NO1 客户总是抱怨价格太高&#xff0c;我常以我们产品质量过硬作为回应。但自从我进入贸易公司后&#xff0c;才真正意识到&#xff0c;在商业世界里&#xff0c;价格才是王道。 NO2 如果顾客提出要去工厂检…...

Linux---进程(3)---进程状态

目录 进程排队 进程状态 运行状态 阻塞状态 挂起状态 Linux内核具体进程状态 浅度睡眠状态 运行状态 深度睡眠状态 暂停状态 可被追踪的暂停状态 终止状态 僵尸状态 进程排队 进程不是一直在运行的&#xff0c;进程放在了CPU上&#xff0c;也不是一直运行的。 进程…...

Drools规则引擎实现停车计费

业务规则: 20:00至次日7时不收费白天7:00-20:00每小时5元,每半个小时计费一次进场30分钟内不收费,但计入时间每天最高收费50元 测试项目搭建 pom<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/…...

【python虚拟环境】安装第三方包失败/failed with error code1

问题&#xff1a; 今天新建了一个项目&#xff0c;默认的虚拟环境pip包版本是19.0.3&#xff0c;太低了。安装第三方包的时候一直超时 解决方案: 更新pip&#xff1a; python -m pip install -U --force-reinstall pip然后就可以正常pip install包了 清华镜像源&#xff1…...

DiffusionModel-latent diffusion,VAE,U-Net,Text-encoder

Diffusers StableDdiffusion 参考: Stable Diffusion原理详解&#xff08;附代码实现) Latent Diffusion 自编码器&#xff08;Variational Autoencoder, VAE&#xff09;&#xff1a; 自编码器是一种无监督学习的神经网络&#xff0c;用于学习数据的有效表示或编码。在稳定扩…...

C# form的移植工作

前言&#xff1a; 目标&#xff0c;将一个项目的form移植到新的工程下&#xff0c;且能够正确编译执行&#xff1a; 1 Copy form的两个文件到新工程下&#xff1a; 比如笔者的logo form 2 修改命名空间&#xff1a; 然后&#xff0c;找到新项目的主程序&#xff1a; 的命名…...

linux防火墙相关命令

防火墙启动关闭 启动防火墙 systemctl start firewalld 关闭防火墙 systemctl stop firewalld 查看状态 systemctl status firewalld 开放或限制端口 开放端口 firewall-cmd --zonepublic --add-port22/tcp --permanent 重新载入一下防火墙设置&#xff0c;使设置生效…...

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

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

ElasticSearch入门语法基础知识

1、创建测试索引 PUT /test_index_person {"settings": {"analysis": {"analyzer": {"ik_analyzer": {"type": "custom","tokenizer": "ik_smart"}}}},"mappings": {"proper…...

【C++】C++应用案例-dolphin海豚记账本

目录 一、整体介绍 1.1、需求和目标 1.2、整体功能描述 二、页面及功能描述 2.1 主菜单 2.2 记账菜单 2.3 查询菜单 2.4 退出功能 三、流程设计 3.1 主流程 3.2 记账操作流程 3.3 查询操作流程 四、代码设计 4.1 核心思路 4.2 项目文件分类设计 4.2.1 头文件 …...

Matlab数据处理学习笔记

1 &#xff1a;数据清洗 注&#xff1a;数据读取 &#xff08;1&#xff09;读取工作表 % 指定要读取的工作表 filename sales_data.xlsx; sheetName Sheet2; % 或者使用工作表编号&#xff0c;例如&#xff1a;sheetNumber 2;% 读取指定工作表的数据 data readtable(fi…...

浏览器中的同源策略、CORS 以及相关的 Fetch API 使用

前言 笔者对前端 Web 技术的认真学习&#xff0c;其实开始于与 Fetch API 的邂逅。当时觉得 fetch() 的设计很不错&#xff0c;也很希望能够请求其它网站下的数据并作处理和展示。学习过程中 HTML 和 CSS 都还好说&#xff0c;由于几乎没有 Web 技术的基础&#xff0c;学习 Fe…...

宁波手机建站模板/山西seo基础教程

QTabWidget 前言 QTabWidget控件提供了一个选项卡和一个页面区域&#xff0c;默认显示第一个选项卡的页面&#xff0c;通过单击各选项卡可以查看对应的界面&#xff0c;如果在一个窗口中显示的输入字段很多&#xff0c;则可以对这些字段进行拆分&#xff0c;分别放置在不同界面…...

vultr安装wordpress/30条新闻摘抄

最近遇到个isa无法连接配置存储的问题&#xff0c;最初发现设置isa策略不生效&#xff0c;通过日志检查到有些策略已经删除&#xff0c;但是还在应用&#xff0c;用户连接进来不通过新的策略检查isa时候发现警告&#xff1a;警报信息 描述: ISA 服务器无法连接到配置存储服务器…...

北京手机模板建站/广州百度推广优化排名

2017年计算机等级考试已经结束&#xff0c;出国留学网为考生们整理了2018年计算机二级考试《C语言》考点&#xff0c;希望能帮到大家&#xff0c;想了解更多资讯&#xff0c;请关注我们&#xff0c;小编会第一时间更新哦。2018年计算机二级考试《C语言》考点&#xff1a;循环结…...

网站制作广/网站备案查询系统

1、获取企业微信信息 1&#xff09;我的企业 --> 企业信息 --> 企业ID 2&#xff09;创建一个应用&#xff0c;记录该应用的 AgentId、Secret 2、python代码 #!/usr/bin/env python # -*- coding:utf-8 -*-import json import requestsclass WeChat:def __init__(self):s…...

网站的建设流程图/最新新闻热点事件2023

一.什么是Prometheus&#xff1f; 一个开源的 Docker 容器监控工具&#xff0c;基于时间序列。 Prometheus 起初是 SoundCloud 创建的一个开源系统监控报警工具。自其 2012 年开创以来&#xff0c;众多公司、组织都采用了 Prometheus&#xff0c;该项目也有一个非常活跃的开发…...

网站怎么做sem/网站优化排名金苹果系统

建立一个JDBC应用程序&#xff0c;本教程中以Java连接MySQL为一个示例&#xff0c;分六个步骤进行&#xff1a; 1. 导入包 在程序中包含数据库编程所需的JDBC类。大多数情况下&#xff0c;使用 import java.sql.* 就足够了&#xff0c;如下所示&#xff1a; //STEP 1. Import r…...