【设计模式】如何用C++实现观察者模式【发布订阅机制】
【设计模式】如何用C++实现观察者模式【发布订阅机制】
一、问题背景
代码质量影响生活质量。最近工作中频繁接触各种设计模式,深刻体会到优秀的设计模式不仅能显著降低后续维护的压力,还能提升开发效率。观察者模式作为一种降低耦合度、提高扩展性的利器,其设计模式思想让人受益匪浅。
过去曾读过一本《练习的心态》,书中提出了这样的问题:当我们专注于过程时,往往会偏离目标,越专注离目标也就越远;当我们关注目标时,要么无法专注于过程,要么因目标与现实的差距而产生放弃的念头。
如何把握过程与目标的关系,也许需要我们用一种观察者的智慧和心态,即同时使用两个视角来思考:
- 第一种视角作为被观察者,允许自身专注,沉浸于过程,最好进入心流状态。
- 第二种视角作为观察者,观察者观察沉浸于过程的执行者,时常检查执行者是否偏离目标,然后提醒执行者调整策略。
当掌握这两种视角后,做到极致的情况下,自身始终存在一种理性的观察者视角:当自身愤怒时,观察者能提供一种理性的视角,以避免行为造成无法接受的后果。如果能做到这一点,仅仅是愤怒的话又有何不可呢。
观察者模式中这两种视角是低耦合的,而作为普通人,难免经常会将两种视角混淆在一起。
”破山中贼易,破心中贼难“,要让心中始终存在一种理性的智慧,也许还需要在事上学、心上练。把握人性的度量,从而实现“从心所欲而不逾矩”的理想人格。
二、什么是观察者模式?
观察者模式(Observer Pattern)是一种常见的设计模式,属于行为型模式。它定义了一种一对多的依赖关系,使得当一个对象的状态发生改变时,其所有依赖对象(观察者)都会自动收到通知并更新。这种模式常用于事件驱动的系统。
观察者模式的核心概念:
- 被观察者(Subject):也称为发布者,它维护着一个观察者列表,当它的状态发生变化时,会通知所有观察者。
- 观察者(Observer):也称为订阅者,它订阅了某个被观察者,当被观察者状态发生变化时,它会收到通知并执行相应的操作。
三、观察者模式工作原理
-
注册观察者: 观察者向被观察者注册,把自己添加到被观察者的观察者列表中。
-
状态改变: 被观察者的状态发生变化。
-
通知观察者: 被观察者遍历观察者列表,依次通知所有注册的观察者。
-
更新: 观察者收到通知后,根据自己的逻辑进行更新。
四、为什么使用观察者模式?
-
低耦合: 被观察者和观察者之间是低耦合的,它们之间不需要知道对方的具体实现细节。
-
可扩展性: 可以动态地增加或删除观察者,而不需要修改被观察者或其他观察者的代码。
-
复用性: 观察者模式可以被应用于各种场景,提高代码的复用性。
五、实现步骤
以发布订阅为例,需要实现以下三个组件:
- Publisher:用于发布订阅事件,承担了观察者模式中的Subject的状态改变功能。
- TopicServer:用于中心化管理Topic订阅事件和Subscriber观察者列表,承担了中的Subject的通知功能。
- Subscriber:用于订阅Topic,当发布者Publisher发布消息时,订阅服务器TopicServer会通知所有订阅该Topic的订阅者Subscriber。
1. 订阅者类Subscriber
./subscribe/Subscribe.h
#ifndef SUBSCRIBE_H
#define SUBSCRIBE_H
#include <string>
namespace Observer
{class Subscriber{public:virtual ~Subscriber() = default;virtual void Update(const std::string &topic, const std::string &message) = 0;};
}
#endif
./subscribe/SubscribeImpl.h
#ifndef SUBSCRIBEIMPL_H
#define SUBSCRIBEIMPL_H
#include <Subscriber.h>
namespace Observer
{class SubscriberImpl : public Subscriber{public:explicit SubscriberImpl(const std::string& subscriberName);~SubscriberImpl() override;void Update(const std::string &topic, const std::string &message) override;private:std::string m_name;};
}
#endif
./subscribe/SubscribeImpl.cpp
#include <SubscriberImpl.h>
#include <iostream>
using namespace Observer;SubscriberImpl::SubscriberImpl(const std::string& subscriberName) : m_name(subscriberName) {}SubscriberImpl::~SubscriberImpl() {}void SubscriberImpl::Update(const std::string &topic, const std::string &message)
{std::cout << "Subscriber [" << m_name << "] received message on topic [" << topic << "]: " << message << std::endl;
}
2. 发布订阅中心类TopicServer
./topicServer/TopicServer.h
#ifndef TOPICSERVER_H
#define TOPICSERVER_H
#include <string>
#include <memory>
#include <Subscriber.h>
namespace Observer
{class TopicServer{public:virtual ~TopicServer() = default;virtual void Subscribe(const std::string& topic, std::shared_ptr<Subscriber> subscriber) = 0;virtual void Unsubscribe(const std::string& topic, std::shared_ptr<Subscriber> subscriber) = 0;virtual void Notify(const std::string& topic, const std::string& message) = 0;};
}
#endif
./topicServer/TopicServerImpl.h
#ifndef TOPICSERVERIMPL_H
#define TOPICSERVERIMPL_H
#include <TopicServer.h>
#include <unordered_map>
#include <vector>
namespace Observer
{class TopicServerImpl : public TopicServer{public:explicit TopicServerImpl();~TopicServerImpl() override;void Subscribe(const std::string& topic, std::shared_ptr<Subscriber> subscriber) override;void Unsubscribe(const std::string& topic, std::shared_ptr<Subscriber> subscriber) override;void Notify(const std::string& topic, const std::string& message) override;private:std::unordered_map<std::string, std::vector<std::shared_ptr<Subscriber>>> m_topicSubscriber;};
}
#endif
./topicServer/TopicServerImpl.cpp
#include <TopicServerImpl.h>
#include <algorithm>
using namespace Observer;TopicServerImpl::TopicServerImpl() {}TopicServerImpl::~TopicServerImpl() {}void TopicServerImpl::Subscribe(const std::string& topic, std::shared_ptr<Subscriber> subscriber)
{m_topicSubscriber[topic].push_back(subscriber);
}void TopicServerImpl::Unsubscribe(const std::string& topic, std::shared_ptr<Subscriber> subscriber)
{auto& subscribersList = m_topicSubscriber[topic];auto itBegin = std::remove(subscribersList.begin(), subscribersList.end(), subscriber);subscribersList.erase(itBegin, subscribersList.end());if (subscribersList.size() == 0) {m_topicSubscriber.erase(topic);}
}void TopicServerImpl::Notify(const std::string& topic, const std::string& message)
{if (m_topicSubscriber.contains(topic)) {for (auto subscriber : m_topicSubscriber[topic]) {subscriber->Update(topic, message);}}
}
3. 发布者类Publisher
./publisher/Publisher.h
#ifndef PUBLISHER_H
#define PUBLISHER_H
#include <string>
namespace Observer
{class Publisher{public:virtual ~Publisher() = default;virtual void PublishMessage(const std::string& topic, const std::string& message) = 0;};
}
#endif
/publisher/PublisherImpl.h
#ifndef PUBLISHERIMPL_H
#define PUBLISHERIMPL_H
#include <Publisher.h>
#include <TopicServer.h>
#include <memory>
namespace Observer
{class PublisherImpl : public Publisher{public:explicit PublisherImpl(std::shared_ptr<TopicServer> topicServer);~PublisherImpl() override;void PublishMessage(const std::string& topic, const std::string& message) override;private:std::shared_ptr<TopicServer> m_server;};
}
#endif
/publisher/PublisherImpl.cpp
#include <PublisherImpl.h>
using namespace Observer;PublisherImpl::PublisherImpl(std::shared_ptr<TopicServer> topicServer) : m_server(topicServer) {}PublisherImpl::~PublisherImpl() {}void PublisherImpl::PublishMessage(const std::string& topic, const std::string& message) {m_server->Notify(topic, message);
}
4. main函数调用
./main.cpp
#include <PublisherImpl.h>
#include <TopicServerImpl.h>
#include <SubscriberImpl.h>
namespace {constexpr std::string TOPICA {"TopicA"};constexpr std::string TOPICB {"TopicB"};
}
using namespace Observer;
int main()
{std::shared_ptr<TopicServer> server = std::make_shared<TopicServerImpl>();// 创建观察者std::shared_ptr<Subscriber> subscriber1 = std::make_shared<SubscriberImpl>("subscriber1");std::shared_ptr<Subscriber> subscriber2 = std::make_shared<SubscriberImpl>("subscriber2");std::shared_ptr<Subscriber> subscriber3 = std::make_shared<SubscriberImpl>("subscriber3");std::shared_ptr<Subscriber> subscriber4 = std::make_shared<SubscriberImpl>("subscriber4");// 订阅主题server->Subscribe(TOPICA, subscriber1);server->Subscribe(TOPICA, subscriber2);server->Subscribe(TOPICA, subscriber4);server->Subscribe(TOPICB, subscriber1);server->Subscribe(TOPICB, subscriber3);server->Subscribe(TOPICB, subscriber4);std::shared_ptr<Publisher> publisher = std::make_shared<PublisherImpl>(server);// 发布消息publisher->PublishMessage(TOPICA, "Hello from TopicA!");publisher->PublishMessage(TOPICB, "Greetings from TopicB!");// 移除订阅者server->Unsubscribe(TOPICA, subscriber4);server->Unsubscribe(TOPICB, subscriber4);// 发布消息publisher->PublishMessage(TOPICA, "Update from TopicA after unsubscribe!");publisher->PublishMessage(TOPICB, "Update from TopicB after unsubscribe!");return 0;
}
5. 编写CMakeLists.txt
cmake_minimum_required(VERSION 3.10)
set(ProjectName Observer)
project(${ProjectName})set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED True)include_directories(./publisher./subscriber./topicServer
)file(GLOB LIB_FILEpublisher/*subscriber/*topicServer/*)message(${LIB_FILE})
add_executable(${ProjectName}main.cpp${LIB_FILE})
此时文件树结构如下:
.
├── CMakeLists.txt
├── main.cpp
├── publisher
│ ├── Publisher.h
│ ├── PublisherImpl.cpp
│ └── PublisherImpl.h
├── subscriber
│ ├── Subscriber.h
│ ├── SubscriberImpl.cpp
│ └── SubscriberImpl.h
└── topicServer├── TopicServer.h├── TopicServerImpl.cpp└── TopicServerImpl.h
6. 编译运行
mkdir build
cd build
cmake ..
make -j12
./Observer
运行结果如下
Subscriber [subscriber1] received message on topic [TopicA]: Hello from TopicA!
Subscriber [subscriber2] received message on topic [TopicA]: Hello from TopicA!
Subscriber [subscriber4] received message on topic [TopicA]: Hello from TopicA!
Subscriber [subscriber1] received message on topic [TopicB]: Greetings from TopicB!
Subscriber [subscriber3] received message on topic [TopicB]: Greetings from TopicB!
Subscriber [subscriber4] received message on topic [TopicB]: Greetings from TopicB!
Subscriber [subscriber1] received message on topic [TopicA]: Update from TopicA after unsubscribe!
Subscriber [subscriber2] received message on topic [TopicA]: Update from TopicA after unsubscribe!
Subscriber [subscriber1] received message on topic [TopicB]: Update from TopicB after unsubscribe!
Subscriber [subscriber3] received message on topic [TopicB]: Update from TopicB after unsubscribe!
运行结果说明:
订阅了TOPICA
的subscriber1、subscriber2、subscriber4
均收到了Publisher
发布的Hello from TopicA!
订阅了TOPICB
的subscriber1、subscriber3、subscriber4
均收到了Publisher
发布的Greetings from TopicB!
subscriber4
对TOPICA
和TOPICB
去订阅后,再次发布消息则只有subscriber1、subscriber2、subscriber3
能收到订阅信息
本文基于观察者模式,侧重于于阐述设计模式的核心思想,实现了一个简化的发布订阅系统。这种设计模式在实际生产环境中,往往需要更复杂的实现,比如涉及到不同进程之间的通信、负载均衡等,以满足高并发、高可用性的要求。
相关文章:
【设计模式】如何用C++实现观察者模式【发布订阅机制】
【设计模式】如何用C实现观察者模式【发布订阅机制】 一、问题背景 代码质量影响生活质量。最近工作中频繁接触各种设计模式,深刻体会到优秀的设计模式不仅能显著降低后续维护的压力,还能提升开发效率。观察者模式作为一种降低耦合度、提高扩展性的利器…...
【LC】2717. 半有序排列
题目描述: 给你一个下标从 0 开始、长度为 n 的整数排列 nums 。 如果排列的第一个数字等于 1 且最后一个数字等于 n ,则称其为 半有序排列 。你可以执行多次下述操作,直到将 nums 变成一个 半有序排列 : 选择 nums 中相邻的两…...

AI智算-k8s部署大语言模型管理工具Ollama
文章目录 简介k8s部署OllamaOpen WebUI访问Open-WebUI 简介 Github:https://github.com/ollama/ollama 官网:https://ollama.com/ API:https://github.com/ollama/ollama/blob/main/docs/api.md Ollama 是一个基于 Go 语言开发的可以本地运…...
CloudberryDB(二) 演化路线图
CloudberryDB 制定了演化路线图(https://github.com/orgs/cloudberrydb/discussions/369)并在逐步改进,这是 Cloudberry Database 发挥独特价值之处。 计划、正在进行或已完成的一些工作。 支持轻松升级 PostgreSQL 内核版本。 原有 Greenp…...

《拉依达的嵌入式\驱动面试宝典》—C/CPP基础篇(二)
《拉依达的嵌入式\驱动面试宝典》—C/CPP基础篇(二) 你好,我是拉依达。 感谢所有阅读关注我的同学支持,目前博客累计阅读 27w,关注1.5w人。其中博客《最全Linux驱动开发全流程详细解析(持续更新)-CSDN博客》已经是 Linux驱动 相关内容搜索的推荐首位,感谢大家支持。 《拉…...

实现canal监控binlog日志再通过消息队列异步处理
一、简单步骤 实现Canal监控Binlog日志,并通过消息队列进行异步处理,步骤如下: 配置Canal:首先,需要配置Canal进行Binlog日志监控。可以通过Canal的官方文档了解如何配置Canal。 连接到Canal:使用Canal客户…...

Linux DNS 协议概述
1. DNS 概述 互联网中,一台计算机与其他计算机通信时,通过 IP 地址唯一的标志自己。此时的 IP 地址就类似于我们日常生活中的电话号码。但是,这种纯数字的标识是比较难记忆的,而且数量也比较庞大。例如,每个 IPv4 地址…...

linux打包qt程序
参考这篇文章:Linux下Qt程序打包_linuxdeployqt下载-CSDN博客 本篇文章的系统环境是 : 虚拟机ubuntu18.04 用下面这个qmake路径 进行编译 在 ~/.bashrc 文件末尾,qmake目录配置到文件末尾 将上图中bin目录下的linuxdeployqt程序拷贝到/usr/bin下一份 &…...

软考中级-软件设计师通过心路经验分享
执念,第四次终于通过了 没买书,下班后每天2小时,四个2个月终于过了 学习经验: 1.下班后学习真的靠毅力,和上学的时候考证不是一个状态,大家要及时调整,否则过程很痛苦 2.失败三次的经验…...

safe area helper插件
概述 显示不同机型的必能显示的区域 实现步骤 引入safearea,引入其中的safearea的csharp 为cancas加入gameobject gameobject中加入safearea脚本 将UI作为这个gameobject的子物体,就可以完成显示...

李宏毅机器学习-批次 (batch)和动量(momentum)
一.batch(批次) 在计算微分时,不是对所有的数据算出来的Loss值做微分,而是将所有的数据分成一个一个的batch。一个batch是一个B,在更新参数时,拿B的资料计算Loss,计算gradient,再更新…...

C# 网络编程--关于UDP 通信(二)
UDP (User Datagram Protocol) 是一种无连接的传输层协议,主要用于支持数据报文的传输。它的主要特点包括简单、高效、不保证可靠性和顺序。 1.UDP协议基本概念 1.udp基于IP的简单的协议,不可靠的协议 2.优点:简单、 轻量化、 传输速度高、…...
【k8s集群应用】Kubernetes部署安装-二进制部署实例
文章目录 Kubernetes 部署方式常见的K8S安装部署方式Kubeadm与二进制部署的区别 Kubernetes部署安装环境配置Kubernetes集群初始化配置(实验环境)一、操作系统初始化配置二、部署Docker引擎 etcd 集群搭建配置 etcd 集群 Kubernetes Master 组件部署准备…...
js常见代码输出问题之promise,await,变量提升以及闭包(包括例子以及详细解析)
这里写目录标题 异步事件循环宏任务微任务1. 执行顺序2. 分类 Promise代码输出1. promise.then执行时机2. 宏任务微任务的多轮次3. .then .catch会返回新的promise4. 返回任意一个非 promise 的值都会被包裹成 promise 对象5. .then .catch 的值不能是promise本身6. 值透传7. .…...

遗传算法与深度学习实战(27)——进化卷积神经网络
遗传算法与深度学习实战(27)——进化卷积神经网络 0. 前言1. 自定义交叉算子2. 自定义突变操作符3. 进化卷积神经网络小结系列链接 0. 前言 DEAP toolbox 中提供的标准遗传操作符对于自定义的网络架构基因序列来说是不够的。这是因为任何标准的交叉算子…...

【Vue3】前端使用 FFmpeg.wasm 完成用户视频录制,并对视频进行压缩处理
强烈推荐这篇博客!非常全面的一篇文章,本文是对该博客的简要概括和补充,在不同技术栈中提供一种可行思路,可先阅读该篇文章再阅读本篇: FFmpeg——在Vue项目中使用FFmpeg(安装、配置、使用、SharedArrayBu…...

基础算法——前缀和
由于比赛基本都是采用Dev-C所以,算法篇基本都是采用Dev-C来解释(版本5.11,c11) 首先介绍一下前缀和算法 给定一个数组,有q次询问,每次询问: 两个整数l,r,求出数组 l 到 r的结果 遇…...

spring实例化对象的几种方式(使用XML配置文件)
前言 Spring框架作为一个轻量级的控制反转(IoC)容器,为开发者提供了多种对象实例化的策略。通过这些策略,开发者可以更加灵活地控制对象的生命周期和依赖关系。无论是通过XML配置、注解配置还是Java配置,Spring都能…...

【二叉树】力扣 129.求根节点到叶子节点数字之和
一、题目 二、思路 每找到一个非空节点,之前路径上的所有节点的数量级都要增加1个单位。例如,当前节点为3,之前的节点路径为1 -> 2,presum 1 * 10 2 12,现在路径变为了 1 -> 2 -> 3,sum pres…...

深度学习物体检测之YOLOV5源码解读
V5比前面版本偏工程化,项目化,更贴合实战 一.V5版本项目配置 (1)整体项目概述 首先github直接查找yolov5,下载下来即可。在训练时,数据是怎么处理的?网络模型架构是怎么设计的(如各层的设计)?yolov5要求是大于python3.8与大于等…...
在软件开发中正确使用MySQL日期时间类型的深度解析
在日常软件开发场景中,时间信息的存储是底层且核心的需求。从金融交易的精确记账时间、用户操作的行为日志,到供应链系统的物流节点时间戳,时间数据的准确性直接决定业务逻辑的可靠性。MySQL作为主流关系型数据库,其日期时间类型的…...

智慧工地云平台源码,基于微服务架构+Java+Spring Cloud +UniApp +MySql
智慧工地管理云平台系统,智慧工地全套源码,java版智慧工地源码,支持PC端、大屏端、移动端。 智慧工地聚焦建筑行业的市场需求,提供“平台网络终端”的整体解决方案,提供劳务管理、视频管理、智能监测、绿色施工、安全管…...

学习STC51单片机31(芯片为STC89C52RCRC)OLED显示屏1
每日一言 生活的美好,总是藏在那些你咬牙坚持的日子里。 硬件:OLED 以后要用到OLED的时候找到这个文件 OLED的设备地址 SSD1306"SSD" 是品牌缩写,"1306" 是产品编号。 驱动 OLED 屏幕的 IIC 总线数据传输格式 示意图 …...

UR 协作机器人「三剑客」:精密轻量担当(UR7e)、全能协作主力(UR12e)、重型任务专家(UR15)
UR协作机器人正以其卓越性能在现代制造业自动化中扮演重要角色。UR7e、UR12e和UR15通过创新技术和精准设计满足了不同行业的多样化需求。其中,UR15以其速度、精度及人工智能准备能力成为自动化领域的重要突破。UR7e和UR12e则在负载规格和市场定位上不断优化…...

第 86 场周赛:矩阵中的幻方、钥匙和房间、将数组拆分成斐波那契序列、猜猜这个单词
Q1、[中等] 矩阵中的幻方 1、题目描述 3 x 3 的幻方是一个填充有 从 1 到 9 的不同数字的 3 x 3 矩阵,其中每行,每列以及两条对角线上的各数之和都相等。 给定一个由整数组成的row x col 的 grid,其中有多少个 3 3 的 “幻方” 子矩阵&am…...

保姆级教程:在无网络无显卡的Windows电脑的vscode本地部署deepseek
文章目录 1 前言2 部署流程2.1 准备工作2.2 Ollama2.2.1 使用有网络的电脑下载Ollama2.2.2 安装Ollama(有网络的电脑)2.2.3 安装Ollama(无网络的电脑)2.2.4 安装验证2.2.5 修改大模型安装位置2.2.6 下载Deepseek模型 2.3 将deepse…...

[ACTF2020 新生赛]Include 1(php://filter伪协议)
题目 做法 启动靶机,点进去 点进去 查看URL,有 ?fileflag.php说明存在文件包含,原理是php://filter 协议 当它与包含函数结合时,php://filter流会被当作php文件执行。 用php://filter加编码,能让PHP把文件内容…...
比较数据迁移后MySQL数据库和OceanBase数据仓库中的表
设计一个MySQL数据库和OceanBase数据仓库的表数据比较的详细程序流程,两张表是相同的结构,都有整型主键id字段,需要每次从数据库分批取得2000条数据,用于比较,比较操作的同时可以再取2000条数据,等上一次比较完成之后,开始比较,直到比较完所有的数据。比较操作需要比较…...
python爬虫——气象数据爬取
一、导入库与全局配置 python 运行 import json import datetime import time import requests from sqlalchemy import create_engine import csv import pandas as pd作用: 引入数据解析、网络请求、时间处理、数据库操作等所需库。requests:发送 …...
redis和redission的区别
Redis 和 Redisson 是两个密切相关但又本质不同的技术,它们扮演着完全不同的角色: Redis: 内存数据库/数据结构存储 本质: 它是一个开源的、高性能的、基于内存的 键值存储数据库。它也可以将数据持久化到磁盘。 核心功能: 提供丰…...