【设计模式】如何用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与大于等…...
golang循环变量捕获问题
在 Go 语言中,当在循环中启动协程(goroutine)时,如果在协程闭包中直接引用循环变量,可能会遇到一个常见的陷阱 - 循环变量捕获问题。让我详细解释一下: 问题背景 看这个代码片段: fo…...
多场景 OkHttpClient 管理器 - Android 网络通信解决方案
下面是一个完整的 Android 实现,展示如何创建和管理多个 OkHttpClient 实例,分别用于长连接、普通 HTTP 请求和文件下载场景。 <?xml version"1.0" encoding"utf-8"?> <LinearLayout xmlns:android"http://schemas…...
spring:实例工厂方法获取bean
spring处理使用静态工厂方法获取bean实例,也可以通过实例工厂方法获取bean实例。 实例工厂方法步骤如下: 定义实例工厂类(Java代码),定义实例工厂(xml),定义调用实例工厂ÿ…...
IoT/HCIP实验-3/LiteOS操作系统内核实验(任务、内存、信号量、CMSIS..)
文章目录 概述HelloWorld 工程C/C配置编译器主配置Makefile脚本烧录器主配置运行结果程序调用栈 任务管理实验实验结果osal 系统适配层osal_task_create 其他实验实验源码内存管理实验互斥锁实验信号量实验 CMISIS接口实验还是得JlINKCMSIS 简介LiteOS->CMSIS任务间消息交互…...
什么?连接服务器也能可视化显示界面?:基于X11 Forwarding + CentOS + MobaXterm实战指南
文章目录 什么是X11?环境准备实战步骤1️⃣ 服务器端配置(CentOS)2️⃣ 客户端配置(MobaXterm)3️⃣ 验证X11 Forwarding4️⃣ 运行自定义GUI程序(Python示例)5️⃣ 成功效果:def __in…...
【VLNs篇】07:NavRL—在动态环境中学习安全飞行
项目内容论文标题NavRL: 在动态环境中学习安全飞行 (NavRL: Learning Safe Flight in Dynamic Environments)核心问题解决无人机在包含静态和动态障碍物的复杂环境中进行安全、高效自主导航的挑战,克服传统方法和现有强化学习方法的局限性。核心算法基于近端策略优化…...
招商蛇口 | 执笔CID,启幕低密生活新境
作为中国城市生长的力量,招商蛇口以“美好生活承载者”为使命,深耕全球111座城市,以央企担当匠造时代理想人居。从深圳湾的开拓基因到西安高新CID的战略落子,招商蛇口始终与城市发展同频共振,以建筑诠释对土地与生活的…...
android13 app的触摸问题定位分析流程
一、知识点 一般来说,触摸问题都是app层面出问题,我们可以在ViewRootImpl.java添加log的方式定位;如果是touchableRegion的计算问题,就会相对比较麻烦了,需要通过adb shell dumpsys input > input.log指令,且通过打印堆栈的方式,逐步定位问题,并找到修改方案。 问题…...
