设计模式之观察者模式
背景
假如我们现在有这样一个场景:市场上的股票价格不定时变化,而后台监控者和广告想要实时获取股票信息,我们应该怎么做?
显然在这个场景里,我们有这样一个设计
- 一个股票类不时更新股票价格
- 另外有两个类Monitor和BillBoard实时接收股票价格的更新并显示出来
基于以上想法,我们先写出一版代码出来
class Monitor {public:void printPrice(int price) {std::cout << "monitor price >>> " << price << std::endl;}
};class BillBoard {public:void displayPrice(int price) {std::cout << "BillBoard price >>> " << price << std::endl;}
};
class Stock {public://实时更新价格,并价格信息反馈给Monitor和BillBoardvoid set_price(int price) {price_ = price;monitor_->printPrice(price);billboard_->displayPrice(price);}private:int price_;std::unique_ptr<Monitor> monitor_;std::unique_ptr<BillBoard> billboard_;
};
void test() {Stock stock;stock.set_price(20);
}
上述代码显然是可以运行的,当Stock修改价格信息时,Monitor和BillBoard可以接受到Stock发送给它的价格信息
但上述代码设计的缺陷很明显
- Stock类和其余两个类之间的耦合度过高,假如我们还有其他的类也想要实时获取股票信息,那么每次就都得修改Stock类的代码
- 在实际场景里Stock不可能知道想要获取股票信息的类的方法具体是什么,也就是说printPrice方法和displayPrice对于Stock并不是事先知道的
观察者模式
初步设计
基于上述第二个缺陷,我们可以设计一个虚函数,将Monitor和BillBoard的打印股票信息的方法抽象成一个统计的接口,如下所示
class Observer {public:virtual void update(int) = 0;private:
};class Monitor : public Observer {public:void printPrice(int price) {std::cout << "monitor >>> " << price << std::endl;}void update(int price) override { printPrice(price); }
};class BillBoard : public Observer {public:void displayPrice(int price) {std::cout << "billoard >>> " << price << std::endl;}void update(int price) override { displayPrice(price); }
};
这样,每次新增一个订阅者的时候,Stock就知道调用哪个方法来发送股票信息了
class Stock {public:void set_price(int price) {price_ = price;observer->update(price);}private:int price_;Observer * observer;
};
为了能够一次性通知所有订阅者,我们可以使用一个容器保存所有的订阅者,然后遍历发送消息,像这样
class Stock {public:void notify(int price) {for (auto &obj : observerlist_) {obj->update(price);}}void set_price(int price) {price_ = price;notify(price);}private:int price_;std::list<Observer *> observerlist_;
};
当然,想要将消息发送出去,保存消息的容器当然要有订阅者对象才行,因此我们需要设计两个方法用于添加订阅者和撤销订阅者
class Stock {public://添加订阅者对象void attach(Observer *obj) { observerlist_.emplace_back(obj); }//移除订阅者对象void deattach(Observer *obj) { observerlist_.remove(obj); }//向所有订阅股票信息的订阅者发送消息void notify(int price) {for (auto &obj : observerlist_) {obj->update(price);}}//修改价格void set_price(int price) {price_ = price;notify(price);}private:int price_;std::list<Observer *> observerlist_;
};
void test() {Monitor monitor;BillBoard billBoard;Stock stock;//添加订阅者对象stock.attach(&monitor);stock.attach(&billBoard);//修改价格stock.set_price(100);stock.set_price(10000);
}
编译运行
修改
经过上述设计,我们其实已经完成了大部分工作,但是还有一个问题
Stock是发送消息者,但是当订阅消息者每次想要订阅消息时却要Stock将其添加到自己的列表里,这显然是不合理的,这就好比,我想要获取一个公众号的消息,但是必须要先让公众号知道我的存在,然后把我添加进它的列表里才行
对于发送消息者来说,添加的订阅者太多的时候显然是一种负担,而对于订阅者来说,还需要等待发送者将自己添加成功后才能获取到实时消息
因此,我们应该再次修改代码,更换一下主客角色
class Stock;
class Observer {public:explicit Observer(Stock *stock);virtual ~Observer();virtual void update(int) = 0;protected:Stock *stock_;
};Observer::Observer(Stock *stock) : stock_(stock) { stock_->attach(this); }
Observer::~Observer() { stock_->deattach(this); }
如上所示,我们直接修改观察者的代码,为其添加发送方的对象指针,然后调用发送方的方法把自己添加到发送方的列表里,如此:
- 对于订阅消息的人来说,只需要关心发送方是谁即可
- 而对于发送方则不需要关心订阅者是谁
完整代码如下
class Stock;
class Observer {public:explicit Observer(Stock *stock);virtual ~Observer();virtual void update(int) = 0;protected:Stock *stock_;
};class Monitor : public Observer {public:explicit Monitor(Stock *stock) : Observer(stock) {}void printPrice(int price) {std::cout << "monitor >>> " << price << std::endl;}void update(int price) override { printPrice(price); }
};class BillBoard : public Observer {public:explicit BillBoard(Stock *stock) : Observer(stock) {}void displayPrice(int price) {std::cout << "billoard >>> " << price << std::endl;}void update(int price) override { displayPrice(price); }
};class Stock {public:void attach(Observer *obj) { observerlist_.emplace_back(obj); }void deattach(Observer *obj) { observerlist_.remove(obj); }void notify(int price) {for (auto &obj : observerlist_) {obj->update(price);}}void set_price(int price) {price_ = price;notify(price);}private:int price_;std::list<Observer *> observerlist_;
};Observer::Observer(Stock *stock) : stock_(stock) { stock_->attach(this); }
Observer::~Observer() { stock_->deattach(this); }
我们再来看测试代码
void test() {Stock stock;Monitor monitor(&stock);BillBoard billBoard(&stock);stock.set_price(1);stock.set_price(10);stock.set_price(1000);
}
- 对于订阅者来说,我关注的是股票信息,因此将股票类的实例对象添加进去
- 对于发送方来说,我不需要关心谁订阅了我的消息,我只修改股票信息就行
编译运行
相关文章:
设计模式之观察者模式
背景 假如我们现在有这样一个场景:市场上的股票价格不定时变化,而后台监控者和广告想要实时获取股票信息,我们应该怎么做? 显然在这个场景里,我们有这样一个设计 一个股票类不时更新股票价格另外有两个类Monitor和Bi…...
Vue.js 学习总结(13)—— Vue3 version 计数介绍
前言 Vue3.5 提出了两个重要概念:version计数和双向链表,作为在内存和计算方面性能提升的最大功臣。既然都重要,那就单挑 version 计数来介绍,它在依赖追踪过程中,起到快速判断依赖项有没有更新的作用,所以…...
数据结构 ——— 直接选择排序算法的实现
目录 直接选择排序算法的思想 优化直接选择排序算法的思想 代码实现(默认升序) 直接选择排序算法的思想 直接选择排序算法的思想类似与直接插入排序 区别在于从大到小选择最小的元素或者最大的元素直接放在元素应该停留的位置每次从待排序的元素中选…...
初始Python篇(5)—— 集合
找往期文章包括但不限于本期文章中不懂的知识点: 个人主页:我要学编程(ಥ_ಥ)-CSDN博客 所属专栏: Python 目录 集合 相关概念 集合的创建与删除 集合的操作符 集合的相关操作方法 集合的遍历 集合生成式 列表、元组、字典、集合的…...
基于Qt/C++/Opencv实现的一个视频中二维码解析软件
本文详细讲解了如何利用 Qt 和 OpenCV 实现一个可从视频和图片中检测二维码的软件。代码实现了视频解码、多线程处理和界面更新等功能,是一个典型的跨线程图像处理项目。以下分模块对代码进行解析。 一、项目的整体结构 项目分为以下几部分: 主窗口 (M…...
图片生成视频-右进
右侧进入 ffmpeg -loop 1 -i image.jpg -f lavfi -i colorcblack:s1280x720:d20 -filter_complex "[1:v]formatrgba[bg];[0:v]formatrgba,scale1280:720[img];[bg][img]overlayxif(lt(t,3),W,if(lt(t,8),W-(t-3)*W/5,0)):y(H-h)/2:enablegte(t,3)" -c:v libx264 -t 2…...
Golang的语言特性与鸭子类型
Golang的语言特性与鸭子类型 前言 什么是鸭子类型? Suppose you see a bird walking around in a farm yard. This bird has no label that says ‘duck’. But the bird certainly looks like a duck. Also, he goes to the pond and you notice that he swims l…...
Spring Boot 3.x + OAuth 2.0:构建认证授权服务与资源服务器
Spring Boot 3.x OAuth 2.0:构建认证授权服务与资源服务器 前言 随着Spring Boot 3的发布,我们迎来了许多新特性和改进,其中包括对Spring Security和OAuth 2.0的更好支持。本文将详细介绍如何在Spring Boot 3.x版本中集成OAuth 2.0…...
Centos-stream 9,10 add repo
Centos-stream repo前言 Centos-stream 9,10更换在线阿里云创建一键更换repo 自动化脚本 华为centos-stream 源 , 阿里云centos-stream 源 华为epel 源 , 阿里云epel 源vim /centos9_10_repo.sh #!/bin/bash # -*- coding: utf-8 -*- # Author: make.h...
手机发展史介绍
手机,这个曾经在电影和科幻小说中出现的高科技产品,如今已经渗透进了我们生活的每个角落。从单纯的通讯工具到如今集成了通讯、娱乐、工作、社交等多种功能的智能终端,手机的发展史也是人类科技进步的缩影。本文将从手机的发展历程、技术革新…...
七天掌握SQL--->第五天:数据库安全与权限管理
1.1 用户权限管理 用户权限管理是指控制用户对数据库的访问和操作权限。在MySQL中,可以使用GRANT和REVOKE命令来管理用户权限。 GRANT命令用于授予用户权限。语法如下: GRANT privileges ON database.table TO userhost IDENTIFIED BY password;其中&…...
Oracle-表空间/用户的创建与使用
-- 对象 -- 需要create的都是对象 已学的对象:表 table -- 普通用户 只能查询user开头的数据字典 select tablespace_name from user_tablespaces; -- dba用户才能够查询 select tablespace_name from dba_tablespaces; -- 创建表空间(需要管理员…...
生成对抗网络模拟缺失数据,辅助PAMAP2数据集仿真实验
PAMAP2数据集是一个包含丰富身体活动信息的数据集,它为我们提供了一个理想的平台来开发和测试HAR模型。本文将从数据集的基本介绍开始,逐步引导大家通过数据分割、预处理、模型训练,到最终的性能评估,在接下来的章节中,…...
表格数据处理中大语言模型的微调优化策略研究
论文地址 Research on Fine-Tuning Optimization Strategies for Large Language Models in Tabular Data Processing 论文主要内容 这篇论文的主要内容是研究大型语言模型(LLMs)在处理表格数据时的微调优化策略。具体来说,论文探讨了以下…...
卡达掐发展史
自行车是一种简单而又伟大的交通工具。自从19世纪诞生以来,它不仅改变了人们的出行方式,也深刻地影响了我们的生活方式、城市布局以及健康观念。作为一种绿色、经济的出行工具,自行车至今仍在全球范围内被广泛使用。本文将从自行车的历史、结…...
初学 flutter 问题记录
windows搭建flutter运行环境 一、运行 flutter doctor遇到的问题 Xcmdline-tools component is missingRun path/to/sdkmanager --install "cmdline-tools;latest"See https://developer.android.com/studio/command-line for more details.1)cmdline-to…...
大数据实验4-HBase
一、实验目的 阐述HBase在Hadoop体系结构中的角色;能够掌握HBase的安装和配置方法熟练使用HBase操作常用的Shell命令; 二、实验要求 学习HBase的安装步骤,并掌握HBase的基本操作命令的使用; 三、实验平台 操作系统࿱…...
CentOS:A服务器主动给B服务器推送(上传),B服务器下载A服务器文件(下载)
Linux:常识(bash: ip command not found )_bash: ip: command not found-CSDN博客 rsync 中断后先判断程序是否自动重连:ps aux | grep rsync 查看目录/文件是否被使用(查询线程占用):lsof /usr/local/bin/mongodump/.B_database1.6uRCTp 场景:MongoDB中集合非常大需要…...
如何选择服务器
如何选择服务器 选择服务器时应考虑以下几个关键因素: 性能需求。根据网站的预期流量和负载情况,选择合适的处理器、内存和存储容量。考虑网站是否需要处理大量动态内容或高分辨率媒体文件。 可扩展性。选择一个可以轻松扩展的服务器架构,以便…...
el-table最大高度无法滚动
解决el-table同时使用fixed和计算的最大高度时固定右边的列无法跟随滚动的问题 原因:el-table组件会根据传入的 max-height 计算表格内容部分 和 fixed部分的最大高度,以此来生成滚动条和产生滚动效果,当传入的 max-height 为一个计算的高度…...
YOLO-FaceV2: A Scale and Occlusion Aware Face Detector
《YOLO-FaceV2:一种尺度与遮挡感知的人脸检测器》 1.引言2.相关工作3.YOLO-FaceV23.1网络结构3.2尺度感知RFE模型3.3遮挡感知排斥损失3.4遮挡感知注意力网络3.5样本加权函数3.6Anchor设计策略3.7 归一化高斯Wasserstein距离 4.实验4.1 数据集4.2 训练4.3 消融实验4.3.1 SEAM块4…...
如何使用 Python 开发一个简单的文本数据转换为 Excel 工具
目录 一、准备工作 二、理解文本数据格式 三、开发文本数据转换为Excel工具 读取CSV文件 将DataFrame写入Excel文件 处理其他格式的文本数据 读取纯文本文件: 读取TSV文件: 四、完整代码与工具封装 五、使用工具 六、总结 在数据分析和处理的日常工作中,我们经常…...
全卷积网络(Fully Convolutional Networks, FCN)
全卷积网络(Fully Convolutional Networks, FCN) 什么是 FCN? 全卷积网络(Fully Convolutional Networks, FCN)是一种用于图像语义分割的深度学习模型。与传统的卷积神经网络(CNN)不同&#x…...
【计算机网络】HTTP协议
一、网址 1.URL URL就是我们平时说的网址 2.urlencode/urldecode 像 / ? : 等这样的字符,已经被url当做特殊意义理解了。因此这些字符不能随意出现 如果某个参数中需要带有这些特殊字符,就必须先对特殊字符进行转义 -> urlencode 服务器收到url…...
IT服务团队建设与管理
在 IT 服务团队中,需要明确各种角色。例如系统管理员负责服务器和网络设备的维护与管理;软件工程师专注于软件的开发、测试和维护;运维工程师则保障系统的稳定运行,包括监控、故障排除等。通过清晰地定义每个角色的职责࿰…...
STM32总体架构简单介绍
目录 一、引言 二、STM32的总体架构 1、三个被动单元 (1)内部SRAM (2)内部闪存存储器 (3)AHB到APB的桥(AHB to APBx) 2、四个主动(驱动)单元 &#x…...
C++自动化测试:GTest 与 GitLab CI/CD 的完美融合
在现代软件开发中,自动化测试是保证代码质量和稳定性的关键手段。对于C项目而言,自动化测试尤为重要,它能有效捕捉代码中的潜在缺陷,提高代码的可维护性和可靠性。本文将重点介绍如何在C项目中结合使用Google Test(GTe…...
Python 虚拟环境使用指南
Python 虚拟环境使用指南 博客 一、创建虚拟环境 在开始使用Python虚拟环境之前,我们需要先创建一个新的虚拟环境。 1. 基础创建流程 首先,进入您的项目目录: cd path\to\your\project然后使用以下命令创建虚拟环境: pytho…...
MySQL面试-1
InnoDB中ACID的实现 先说一下原子性是怎么实现的。 事务要么失败,要么成功,不能做一半。聪明的InnoDB,在干活儿之前,先将要做的事情记录到一个叫undo log的日志文件中,如果失败了或者主动rollback,就可以通…...
Harbor2.11.1生成自签证和配置HTTPS访问
文章目录 HTTPS的工作流程部署Harbor可参考上一篇文章生成自签证书1.修改/etc/hosts文件2.生成证书a.创建存放证书路径b.创建ca.key密钥c.创建ca.crtd.创建给Harbor服务器使用密钥 yunzhidong.harbor.com.keye.创建给Harbor服务器使用证书签名请求文件 yunzhidong.harbor.com.c…...
鸿蒙开发Hvigor插件动态生成代码
Hvigor允许开发者实现自己的插件,开发者可以定义自己的构建逻辑,并与他人共享。Hvigor主要提供了两种方式来实现插件:基于hvigorfile脚本开发插件、基于typescript项目开发。下面以基于hvigorfile脚本开发插件进行介绍。 基于hvigorfile脚本…...
VSCode 新建 Python 包/模块 Pylance 无法解析
问题描述: 利用 VSCode 写代码,在项目里新建一个 Python 包或者模块,然后在其他文件里正常导入这个包或者模块时出现: Import “xxxx” could not be resolved Pylance (reportMissingImports) 也就是说 Pylance 此时无法解析我们…...
【论文笔记】Number it: Temporal Grounding Videos like Flipping Manga
🍎个人主页:小嗷犬的个人主页 🍊个人网站:小嗷犬的技术小站 🥭个人信条:为天地立心,为生民立命,为往圣继绝学,为万世开太平。 基本信息 标题: Number it: Temporal Grou…...
【C++】list模拟实现(详解)
本篇来详细说一下list的模拟实现,list的大体框架实现会比较简单,难的是list的iterator的实现。我们模拟实现的是带哨兵位头结点的list。 1.准备工作 为了不和C库里面的list冲突,我们在实现的时候用命名空间隔开。 //list.h #pragma once #…...
Java基础-组件及事件处理(下)
(创作不易,感谢有你,你的支持,就是我前行的最大动力,如果看完对你有帮助,请留下您的足迹) 目录 面板组件 说明 常见组件 JScrollPane常用构造方法 JScrollPane设置面板滚动策略的方法 JScrollPane滚…...
太通透了,Android 流程分析 蓝牙enable流程(应用层/Framework/Service层)
零. 前言 由于Bluedroid的介绍文档有限,以及对Android的一些基本的知识需要了(Android 四大组件/AIDL/Framework/Binder机制/JNI/HIDL等),加上需要掌握的语言包括Java/C/C等,加上网络上其实没有一个完整的介绍Bluedroid系列的文档࿰…...
《硬件架构的艺术》笔记(五):低功耗设计
介绍 能量以热量形式消耗,温度升高芯片失效率也会增加,增加散热片或风扇会增加整体重量和成本,在SoC级别对功耗进行控制就可以减少甚至可能消除掉这些开支,产品也更小更便宜更可靠。本章描述了减少动态功耗和静态功耗的各种技术。…...
【Ubuntu24.04】服务部署(虚拟机)
目录 0 背景1 安装虚拟机1.1 下载虚拟机软件1.2 安装虚拟机软件1.2 安装虚拟电脑 2 配置虚拟机2.1 配置虚拟机网络及运行初始化脚本2.2 配置服务运行环境2.2.1 安装并配置JDK172.2.2 安装并配置MySQL8.42.2.3 安装并配置Redis 3 部署服务4 总结 0 背景 你的服务部署在了你的计算…...
Java基础-组件及事件处理(中)
(创作不易,感谢有你,你的支持,就是我前行的最大动力,如果看完对你有帮助,请留下您的足迹) 目录 BorderLayout布局管理器 说明: 示例: FlowLayout布局管理器 说明: …...
从零开始学习数据库 day0(基础)
在当今的信息时代,数据已经成为了企业和组织最重要的资产之一。无论是电子商务平台,社交媒体,还是科研机构,几乎每个地方都离不开数据库。今天,我们将一起走进数据库的世界,学习它的基础知识,帮…...
【淘汰9成NLP面试者的高频面题】LSTM中的tanh和sigmoid分别用在什么地方?为什么?
博客主页: [青松] 本文专栏: NLP 大模型百面百过 【淘汰9成NLP面试者的高频面题】LSTM中的tanh和sigmoid分别用在什么地方?为什么? 重要性:★★★ 💯 本题主要考察面试者对以下问题的理解: ① 数据特征和模…...
linux从0到1——shell编程9
声明! 学习视频来自B站up主 **泷羽sec** 有兴趣的师傅可以关注一下,如涉及侵权马上删除文章,笔记只是方便各位师傅的学习和探讨,文章所提到的网站以及内容,只做学习交流,其他均与本人以及泷羽sec团队无关&a…...
【源码+文档+调试讲解】基于Hadoop实现的豆瓣电子图书推荐系统的设计与实现
摘 要 随着开数字化阅读的普及,豆瓣电子图书推荐系统应运而生,旨在为用户提供个性化的阅读体验。基于Hadoop的强大数据处理能力,该系统能够有效处理海量用户数据和书籍信息,通过复杂的算法模型为用户推荐高质量的内容。管理员功能…...
内存分配与回收策略
对象优先在Eden分配 大多数情况下,对象在新生代Eden中分配,当Eden区没有足够的空间按进行分配时,虚拟机将会引发一次Minor GC 。 在运行时通过-Xms20M、-Xmx20M、-Xmn10M这三个参数限制了Java 堆大小为20MB,不可扩展,…...
实时数据开发 | 怎么通俗理解Flink容错机制,提到的checkpoint、barrier、Savepoint、sink都是什么
今天学Flink的关键技术–容错机制,用一些通俗的比喻来讲这个复杂的过程。参考自《离线和实时大数据开发实战》 需要先回顾昨天发的Flink关键概念 检查点(checkpoint) Flink容错机制的核心是分布式数据流和状态的快照,从而当分布…...
android-sdk 安装脚本
android-sdk 安装脚本 androidSdk_install.sh #!/bin/bash #[描述] android-sdk 安装# set -eu shopt -s expand_aliasesAndroid_SDK_D/app5/android-sdk-home/JAVA17_D/app/zulu17.48.15-ca-jdk17.0.10-linux_x64/#jdk17下载、解压 #https://www.azul.com/downloads/?version…...
【jvm】解释器
目录 1. 说明2. 工作原理3. 特点4. JVM解释器与JIT编译器的关系5. JVM解释器的优化 1. 说明 1.JVM(Java虚拟机)解释器是JVM的一个重要组成部分,负责将Java字节码指令翻译并执行为本地机器码。 2. 工作原理 1.读取字节码指令:JV…...
Node.js 安装与环境配置详解:从入门到实战
**标题:Node.js 安装与环境配置详解:从入门到实战** --- ### 一、Node.js 简介 Node.js 是一个基于 Chrome V8 引擎的 JavaScript 运行时环境,允许开发者在服务器端运行 JavaScript 代码。凭借其事件驱动、非阻塞 I/O 模型,Nod…...
Centos使用docker搭建Graylog日志平台
日志管理系统有很多,比如ELK,Graylog,LokiGrafanaPromtail 适用场景: 1.如果需求复杂,服务器资源不受限制,推荐使用ELK(Logstash Elasticsearch Kibana)方案; 2.如果需求仅是将…...
Java基础-Java中的常用类(上)
(创作不易,感谢有你,你的支持,就是我前行的最大动力,如果看完对你有帮助,请留下您的足迹) 目录 String类 创建字符串 字符串长度 连接字符串 创建格式化字符串 String 方法 System类 常用方法 方…...