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

设计模式 Day 6:深入讲透观察者模式(真实场景 + 回调机制 + 高级理解)

观察者模式(Observer Pattern)是一种设计结构中最实用、最常见的行为模式之一。它的魅力不仅在于简洁的“一对多”事件推送能力,更在于它的解耦能力、模块协作设计、实时响应能力

本篇作为 Day 6,将带你从理论、底层机制到真实工程项目实战,全方位、系统地掌握观察者模式,彻底吃透其设计价值。


一、重新理解观察者模式的本质

✅ 一句话总结:

观察者模式的核心,是在被观察者状态变化时通知所有关心它的对象,从而构建一个低耦合、响应式的通知机制

📌 抽象结构:

class Subject {
public:void attach(Observer* obs);void detach(Observer* obs);void notify();
};class Observer {
public:virtual void update() = 0;
};

🎯 优点:

  • 一对多广播机制,通知多模块
  • 实现了“订阅/发布”架构模型
  • 观察者动态添加移除,系统更灵活

❗ 本质关键:

  • 状态变更感知 + 自动联动更新
  • 利用回调函数机制(函数指针 / lambda)实现通知解耦

二、现实中典型的观察者模式场景(高频 + 好记 + 实战)

在这里插入图片描述

🌟 1. GUI 事件响应系统(经典)

  • 鼠标点击按钮 → 所有监听该按钮事件的 UI 模块立刻响应

✅ 示例:

button.onClick().connect([]() {std::cout << "按钮被点击,弹窗显示!" << std::endl;
});

🌟 2. 实时行情推送系统(金融/交易所)

  • 价格更新后 → 各个终端(交易页面、预警模块、图表组件)即时响应

✅ 模块划分:

  • MarketDataFeed:行情中心(Subject)
  • ChartUI / Alarm / StrategyModel:观察者

🌟 3. 硬件驱动数据采集(IoT / 医疗监护)

  • 心率变化 → 推送给监护仪屏幕、记录系统、告警系统

🌟 4. 游戏开发:对象状态广播

  • 血量变化 → 渲染模块、AI判断模块、音效模块需同时更新

🌟 5. 插件系统事件通知

  • IDE 插件监听工程加载事件、文件保存事件等

三、观察者模式的核心:回调函数机制

观察者的关键,就是通过函数指针 / 回调函数 / lambda 表达式连接行为与状态变化

✅ 1. 函数指针的基本形式

void (*callback)(int);
callback = myHandler;
callback(123); // 执行

✅ 2. lambda 表达式(现代写法)

auto callback = [](int price) {std::cout << "新价格:" << price << std::endl;
};

✅ 3. std::function + std::bind(成员函数回调)

class Alarm {
public:void trigger(int v) { std::cout << "触发报警:" << v << std::endl; }
};Alarm alarm;
std::function<void(int)> cb = std::bind(&Alarm::trigger, &alarm, std::placeholders::_1);
cb(90);

这些函数式调用,正是观察者通知机制的底层实现核心。


四:Boost.Signals2 的使用原理与机制

Boost.Signals2 是观察者模式在现代 C++ 中的高效、安全实现。它将“通知发布者(Subject)”与“通知接收者(Observer)”的注册、解绑、调用机制封装得更加通用和安全。

✅ 基本原理:

  • signal<T> 类似“事件总线”,可连接多个“响应槽(slot)”
  • connect() 注册 slot
  • operator() 触发信号,自动调用所有 slot

✅ 特点分析:

特性说明
自动解绑支持 scoped_connection、生命周期追踪(weak_ptr)
多线程安全所有操作加锁,适合多线程信号触发
返回值聚合器可对多个 slot 的返回值做统一处理(例如返回第一个、合并结果)
插槽灵活连接支持函数、lambda、成员函数、functor 对象

✅ 例子说明:

boost::signals2::signal<void(int)> sig;sig.connect([](int v) { std::cout << "值为:" << v << std::endl; });sig(42); // 触发所有观察者响应

✅ 自动解绑:

{boost::signals2::scoped_connection conn = sig.connect(...);// conn 析构自动解除绑定
} // 安全退出,不再触发此 slot

✅ 成员函数绑定:

sig.connect(boost::bind(&Class::method, &obj, _1));

Boost.Signals2 的底层结构包含:

  • slot 链表容器:存储所有观察者
  • 线程锁保护:对 connect/emit 操作加锁
  • 断开机制:支持连接手动断开、生命周期关联解绑

它解决了手写观察者中最容易出现的问题:

  • 悬空指针访问
  • 多线程数据竞争
  • 连接管理混乱

因此在现代 C++ 项目中,如果你需要一种安全、可维护、低耦合、线程友好的观察者实现方案,Boost.Signals2 是首选。

五、项目实战:构建一个“传感器驱动 + 多模块响应系统”

📌 需求背景:

工业设备上连接温度传感器,当温度变化时,需要:

  • 屏幕显示实时温度
  • 超过阈值时报警
  • 自动记录进系统日志

🎯 类结构图:

+--------------------+
|  TemperatureSensor |
+--------------------+
| +addListener()     | ← 注册观察者
| +removeListener()  |
| +updateTemp()      | ← 状态变化
| +notify()          |
+--------------------+↓多个监听回调函数(Slot)

✅ C++ 实现(使用 Boost.Signals2):

#include <iostream>
#include <boost/signals2.hpp>class TemperatureSensor {
public:boost::signals2::signal<void(float)> onTempChanged;void updateTemp(float newTemp) {std::cout << "[Sensor] 当前温度:" << newTemp << std::endl;onTempChanged(newTemp);  // 触发信号,通知所有观察者}
};class LCDDisplay {
public:void show(float t) {std::cout << "[LCD] 显示温度:" << t << std::endl;}
};class AlarmModule {
public:void check(float t) {if (t > 80)std::cout << "[Alarm] 温度过高,发出警报!" << std::endl;}
};class Logger {
public:void log(float t) {std::cout << "[Log] 记录温度值:" << t << std::endl;}
};int main() {TemperatureSensor sensor;LCDDisplay lcd;AlarmModule alarm;Logger logger;sensor.onTempChanged.connect([&](float t){ lcd.show(t); });sensor.onTempChanged.connect([&](float t){ alarm.check(t); });sensor.onTempChanged.connect([&](float t){ logger.log(t); });sensor.updateTemp(65.0);sensor.updateTemp(88.2);return 0;
}

✅ 输出示例:

[Sensor] 当前温度:65
[LCD] 显示温度:65
[Log] 记录温度值:65
[Sensor] 当前温度:88.2
[LCD] 显示温度:88.2
[Alarm] 温度过高,发出警报!
[Log] 记录温度值:88.2

六、观察者模式的扩展与变种

✅ 延迟通知 vs 立即通知

  • 有些系统不立即通知,而是打包合并,异步发送 → 对应“批量广播机制”

✅ 支持过滤的观察者(按条件触发)

if (t > 100) observerA();
else observerB();

✅ 支持优先级注册

  • 有些响应函数必须先执行,可加入优先级队列(boost 支持插槽分组)

七、面试与复述技巧

“我们项目中大量使用观察者机制来进行模块间解耦,比如设备数据变化后推送到 UI、日志、预警等模块。为了安全性与性能,我们使用了 Boost.Signals2,实现了自动连接管理与线程安全的广播机制,同时通过 lambda 与 bind 配合,保持代码灵活、结构清晰。”

✅ 加分关键词:事件推送 / 多模块联动 / 自动解绑 / 异步广播 / 回调链路


八、总结记忆要点

模块要素说明
Subject状态持有者,触发变化
Observer回调函数实体,响应变化
通知机制connect → 回调列表 → notify 调用
解耦点无需知道观察者是谁,只要通知
实现方式函数指针 / lambda / bind / signal

✅ 一句话背诵版:

“观察者模式通过回调机制建立一对多解耦通道,实现状态联动与模块协作。”


明日预告:Day 7

策略模式(Strategy Pattern)实战详解:在支付系统、路径规划、压缩算法中优雅切换策略。

相关文章:

设计模式 Day 6:深入讲透观察者模式(真实场景 + 回调机制 + 高级理解)

观察者模式&#xff08;Observer Pattern&#xff09;是一种设计结构中最实用、最常见的行为模式之一。它的魅力不仅在于简洁的“一对多”事件推送能力&#xff0c;更在于它的解耦能力、模块协作设计、实时响应能力。 本篇作为 Day 6&#xff0c;将带你从理论、底层机制到真实…...

C#异步方法返回Task<T>的同步调用

在C#中我们已经非常习惯使用async/await来实现异步调用,但是某些时候并不允许异步调用,比如在一个Dynamics365的插件或操作中 为了确保事务性是不允许异步调用的,这个时候在使用httpclient发起请求时我们就可以使用Task<T>.Result来实现线程阻塞,进行同步方式的调用: va…...

OpenCV 图形API(18)用于执行两个矩阵(或数组)的逐元素减法操作函数sub()

操作系统&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 编程语言&#xff1a;C11 描述 计算两个矩阵之间的逐元素差值。 sub 函数计算两个矩阵之间的差值&#xff0c;要求这两个矩阵具有相同的尺寸和通道数&#xff1a; dst ( I ) src…...

汽车软件开发常用的需求管理工具汇总

目录 往期推荐 DOORS&#xff08;IBM &#xff09; 行业应用企业&#xff1a; 应用背景&#xff1a; 主要特点&#xff1a; Polarion ALM&#xff08;Siemens&#xff09; 行业应用企业&#xff1a; 应用背景&#xff1a; 主要特点&#xff1a; Codebeamer ALM&#x…...

AI 越狱技术剖析:原理、影响与防范

一、AI 越狱技术概述 AI 越狱是指通过特定技术手段&#xff0c;绕过人工智能模型&#xff08;尤其是大型语言模型&#xff09;的安全防护机制&#xff0c;使其生成通常被禁止的内容。这种行为类似于传统计算机系统中的“越狱”&#xff0c;旨在突破模型的限制&#xff0c;以实…...

推荐一款Nginx图形化管理工具: NginxWebUI

Nginx Web UI是一款专为Nginx设计的图形化管理工具&#xff0c;旨在简化Nginx的配置与管理过程&#xff0c;提高开发者和系统管理的工作效率。项目地址&#xff1a;https://github.com/cym1102/nginxWebUI 。 一、Nginx WebUI的主要特点 简化配置&#xff1a;通过图形化的界…...

区间 dp 系列 题解

1.洛谷 P4342 IOI1998 Polygon 我的博客 2.洛谷 P4290 HAOI2008 玩具取名 题意 某人有一套玩具&#xff0c;并想法给玩具命名。首先他选择 W, I, N, G 四个字母中的任意一个字母作为玩具的基本名字。然后他会根据自己的喜好&#xff0c;将名字中任意一个字母用 W, I, N, G …...

Spring Boot 自动加载流程详解

前言 Spring Boot 是一个基于约定优于配置理念的框架&#xff0c;它通过自动加载机制大大简化了开发者的配置工作。本文将深入探讨 Spring Boot 的自动加载流程&#xff0c;并结合源码和 Mermaid 图表进行详细解析。 一、Spring Boot 自动加载的核心机制 Spring Boot 的自动加…...

《从底层逻辑剖析:分布式软总线与传统计算机硬件总线的深度对话》

在科技飞速发展的当下&#xff0c;我们正见证着计算机技术领域的深刻变革。计算机总线作为信息传输的关键枢纽&#xff0c;其发展历程承载着技术演进的脉络。从传统计算机硬件总线到如今备受瞩目的分布式软总线&#xff0c;每一次的变革都为计算机系统性能与应用拓展带来了质的…...

Fay 数字人部署环境需求

D:\ai\Fay>python main.py pygame 2.6.1 (SDL 2.28.4, Python 3.11.9) Hello from the pygame community. https://www.pygame.org/contribute.html [2025-04-11 00:10:16.7][系统] 注册命令... [2025-04-11 00:10:16.8][系统] restart 重启服务 [2025-04-11 00:10:16.8][…...

python:all列表

1.all列表的说明&#xff1a; 当模块中有__all__变量时&#xff0c;当使用from xxx import *时&#xff0c;只能导入这个列表中的元素。 2.具体的例子&#xff1a; 1.先创建一个模块my_mod,在列表__all__中分别写入第一次只写入test1&#xff0c;第二次写入test1、test2两个…...

基于 SpringBoot 的校园论坛系统

收藏关注不迷路&#xff01;&#xff01; &#x1f31f;文末获取源码数据库&#x1f31f; 感兴趣的可以先收藏起来&#xff0c;还有大家在毕设选题&#xff08;免费咨询指导选题&#xff09;&#xff0c;项目以及论文编写等相关问题都可以给我留言咨询&#xff0c;希望帮助更多…...

2025 跨平台技术如何选:KMP 与 Flutter 的核心差异

前言 在移动开发的演进历程中&#xff0c;跨平台技术始终是一个充满争议却无法回避的话题。从早期的 React Native 到如今的 Kotlin Multiplatform&#xff08;KMP&#xff09;和 Flutter&#xff0c;开发者们始终在代码复用与原生体验之间寻找平衡。本文我们从技术实现、性能…...

深度学习总结(6)

随机梯度下降 给定一个可微函数&#xff0c;理论上可以用解析法找到它的最小值&#xff1a;函数的最小值就是导数为0的点&#xff0c;因此只需找到所有导数为0的点&#xff0c;然后比较函数在其中哪个点的取值最小。将这一方法应用于神经网络&#xff0c;就是用解析法求出损失…...

SpringBoot实战1

SpringBoot实战1 一、开发环境&#xff0c;环境搭建-----创建项目 通过传统的Maven工程进行创建SpringBoot项目 &#xff08;1&#xff09;导入SpringBoot项目开发所需要的依赖 一个父依赖&#xff1a;&#xff08;工件ID为&#xff1a;spring-boot-starter-parent&#xf…...

深度学习实战:从零构建图像分类API(Flask/FastAPI版)

引言&#xff1a;AI时代的图像分类需求 在智能时代&#xff0c;图像分类技术已渗透到医疗影像分析、自动驾驶、工业质检等各个领域。作为开发者&#xff0c;掌握如何将深度学习模型封装为API服务&#xff0c;是实现技术落地的关键一步。本文将手把手教你使用Python生态中的Fla…...

【Linux】39.一个基础的HTTP Web服务器

文章目录 1. 实现一个基础的HTTP Web服务器1.1 功能实现&#xff1a;1.2 Log.hpp-日志记录器1.3 HttpServer.hpp-网页服务器1.4 Socket.hpp-网络通信器1.5 HttpServer.cc-服务器启动器 1. 实现一个基础的HTTP Web服务器 1.1 功能实现&#xff1a; 总体功能&#xff1a; 提供We…...

阿里云域名证书自动更新acme.sh

因为阿里云的免费证书只有三个月的有效期&#xff0c;每次更换都比较繁琐&#xff0c;所以找到了 acme.sh&#xff0c;还有一种 certbot 我没有去了解&#xff0c;就直接使用了 acme.sh 来更新证书&#xff0c;acme.sh 的主要特点就是&#xff1a; 支持多种 DNS 服务商自动化续…...

大数据Hadoop(MapReduce)

MapReduce概述 MapReduce定义 MapReduce是一个分布式运算程序的编程框架&#xff0c;是用户开发“基于Hadoop的数据分析应用”的核心框架。 MapReduce核心功能是将用户编写的业务逻辑代码和自带默认组件整合成一个完整的分布式运算程序&#xff0c;并发运行在一个Hadoop集群上…...

图灵逆向——题十七-字体加密

十七题是一个很经典的字体加密案例&#xff0c;很适合新手入门~ 目录列表 过程分析代码实现 过程分析 打开开发者工具直接看请求&#xff0c;发现它请求的没有加密参数&#xff0c;以为万事大吉的你迫不及待的点击了响应&#xff0c;然后就会发现依托。。。 返回的数据中字体…...

(自用)蓝桥杯准备(需要写的基础)

要写的文件 led_app lcd_app key_app adc_app usart_app scheduler LHF_SYS一、外设引脚配置 1. 按键引脚 按键引脚配置如下&#xff1a; B1&#xff1a;PB0B2&#xff1a;PB1B3&#xff1a;PB2B4&#xff1a;PA0 2. LCD引脚 LCD引脚配置如下&#xff1a; GPIO_Pin_9 /* …...

系统与网络安全------网络通信原理(5)

资料整理于网络资料、书本资料、AI&#xff0c;仅供个人学习参考。 传输层解析 传输层 传输层的作用 IP层提供点到点的连接传输层提供端到端的连接 端口到端口的连接&#xff08;不同端口号&#xff0c;代表不同的应用程序&#xff09; TCP协议概述 TCP&#xff08;Transm…...

minio提供nfs服务

minio提供nfs服务 挂载minio为本地目录配置开机自动挂载方法1: 使用supervisor实现开机自动挂载方法2: 服务单元实现开机自动挂载minio为本地目录---失败调试 配置NFS服务端 挂载minio为本地目录 使用 Minio 作为后端存储&#xff0c;并通过 NFS 为客户端提供访问&#xff0c;…...

vue2添加背景水印-手动实现(无组件模式)

1. App.vue <template><div id="app" class="app"><router-view></router-view></div> </template><script> export default {mounted() {this.updateWatermark();// 监听路由变化this.$router.afterEach(() =…...

嵌入式---加速度计

一、基本概念与定义 定义 加速度计&#xff08;Accelerometer&#xff09;是一种测量物体加速度&#xff08;线性加速度或振动加速度&#xff09;的传感器&#xff0c;可检测物体运动状态、振动幅度、倾斜角度等&#xff0c;输出与加速度成比例的电信号&#xff08;模拟或数字信…...

swagger + Document

swagger 虽然有了api接口&#xff0c;对于复杂接口返回值说明&#xff0c;文档还是不能少。如果是一个人做的还简单一点&#xff0c;现在都搞前后端分离&#xff0c;谁知道你要取那个值呢...

【Git】--- 多人协作实战场景

Welcome to 9ilks Code World (๑•́ ₃ •̀๑) 个人主页: 9ilk (๑•́ ₃ •̀๑) 文章专栏&#xff1a; Git 前面我们学习了Git的所有本地仓库的相关操作:git基本操作,分支理解,版本回退,冲突解决等等。同时我们还理解了远端仓库在开发的作用以及相关操作push…...

Higress: 阿里巴巴高性能云原生API网关详解

一、Higress概述 Higress是阿里巴巴开源的一款基于云原生技术构建的高性能API网关&#xff0c;专为Kubernetes和微服务架构设计。它集成了Ingress控制器、微服务网关和API网关功能于一体&#xff0c;支持多种协议和丰富的流量管理能力。 发展历程 Higress 从最初社区的 Isti…...

常见的 set 选项与空变量检查

在编写 Bash 脚本时&#xff0c;使用 set 命令中的一些选项可以帮助我们在脚本执行过程中及时捕获错误和潜在问题&#xff0c;避免脚本在出错时继续执行&#xff0c;提高脚本的可靠性和健壮性。 set -e&#xff1a;遇到错误就停 set -e 的作用是&#xff1a;一旦脚本中的某个…...

leetcode 377. Combination Sum IV

这道题也是完全背包问题。这道题和第518题几乎一摸一样&#xff0c;所不同的是&#xff0c;第518题要求的是组合数&#xff0c;而第377题要求的是排列数。虽然本题题目描述中说求的是组合数&#xff0c;但从例子1中&#xff08;1&#xff0c;1&#xff0c;2&#xff09;和&…...