C 嵌入式系统设计模式 11:观察者模式

本书的原著为:《Design Patterns for Embedded Systems in C ——An Embedded Software Engineering Toolkit 》,讲解的是嵌入式系统设计模式,是一本不可多得的好书。

本系列描述我对书中内容的理解。本文章描述访问硬件的设计模式之四:观察者模式。

在面向对象编程中,观察者模式(Observer Pattern)是一种常见的设计模式。它定义了对象之间的一对多依赖关系,当一个对象的状态发生改变时,其所有依赖者(观察者)都会收到通知并自动更新。

具体来说,观察者模式包含两个主要角色:

  1. 主题(Subject):主题是一个接口,该接口定义了一个注册观察者的方法、一个移除观察者的方法以及一个当主题状态改变时通知所有观察者的方法。主题通常维护一个观察者列表,用于存储注册的观察者对象。
  2. 观察者(Observer):观察者也是一个接口,该接口定义了一个更新方法,用于在主题状态改变时接收通知并更新自己。具体的观察者类需要实现这个接口,并在更新方法中定义自己的响应逻辑。

观察者模式的主要用途是实现对象之间的解耦。通过将对象之间的依赖关系从硬编码的方式转变为动态的方式,观察者模式使得系统的可扩展性和可维护性大大提高。当一个对象的状态发生改变时,它不再需要直接通知所有依赖它的对象,而是只需要通知它的观察者列表中的对象即可。

举个简单的例子来说明观察者模式的应用:假设有一个天气预报系统,其中包含一个天气数据类(主题)和多个显示天气信息的类(观察者)。当天气数据发生变化时,天气数据类会通知所有注册的观察者更新显示内容。这样一来,无论是添加新的显示类还是修改现有的显示类,都不会影响到天气数据类和其他显示类之间的依赖关系。这就是观察者模式的魅力所在。

观察者模式 是软件设计中最常用的模式之一。它提供了一种机制,使得对象能够“监听”或订阅其他对象的状态变化,而无需对数据服务器(主题)进行任何修改。在嵌入式领域中,这意味着传感器数据可以轻松共享给那些在编写传感器代理代码时甚至可能还不存在的元素。

具体来说,在嵌入式系统中,传感器负责收集和提供关于环境或系统状态的信息。传统上,每个需要使用这些信息的组件或模块都需要直接与传感器进行交互,这可能会导致代码之间的紧密耦合和难以维护的问题。

然而,通过使用观察者模式,我们可以将传感器数据与其使用者解耦。传感器可以作为 主题(Subject),而需要使用传感器数据的组件或模块则可以注册为 观察者(Observer)。当传感器数据发生变化时,所有注册的观察者都会收到 通知,并可以根据需要使用这些数据。

这种方式的优点在于,它允许我们在不修改传感器代码的情况下动态地添加或删除观察者。这意味着我们可以轻松地添加新的组件或模块来使用传感器数据,而无需对现有代码进行大量修改。此外,由于观察者模式支持一对多的通信方式,因此多个组件或模块可以同时接收并使用同一份传感器数据。

摘要

观察者模式(也被称为“发布-订阅模式”)是一种设计模式,它让一组感兴趣的客户端(即观察者)能够订阅某个主题(或称为数据服务器)。当该主题的相关数据发生变化时,这些已订阅的客户端会收到通知。此模式的关键之处在于,主题无需事先了解任何关于其客户端的信息。相反,客户端通过主题提供的订阅功能,可以动态地将自己添加到通知列表中,并同样可以从列表中移除。

数据服务器可以根据具体需求执行各种通知策略。最常见的情况是,每当新数据到达时,都会立即通知已订阅的客户端。然而,通知也可以基于特定的策略进行,例如定期更新、按照设定的最小或最大频率进行更新等。这种方式显著降低了客户端的计算负担,因为它们无需持续检查数据是否已更新;相反,它们会在数据实际更新时收到通知,从而实现了更高效的数据处理和资源利用。

在实际应用中,观察者模式广泛应用于各种场景,如用户界面中的事件处理(如按钮点击、文本输入等),股票交易系统中的价格更新通知,或者是实时天气信息更新等。通过这种模式,系统可以更加灵活、可扩展,并且能够更好地处理变化。

问题

在简单情况中,客户端可能会定期从数据服务器请求数据,以检查数据是否发生了变化。然而,这种做法存在计算和通信资源的浪费问题,因为客户端通常无法准确知道何时有新数据可用,导致频繁的无效查询。另一方面,如果数据服务器主动推送数据给客户端,那么服务器就必须维护所有客户端的信息,这违背了客户端-服务器关系的基本原则,即服务器应该无需了解客户端的具体细节。每当添加新客户端时,都需要对服务器进行修改,这显然是不切实际的。

观察者模式通过引入订阅和解除订阅服务来有效地解决这个问题。在这种模式下,客户端可以动态地将自己添加到数据服务器的通知列表中,而无需服务器预先了解客户端的信息。这样一来,服务器就可以在数据发生变化时,根据其维护的通知列表向感兴趣的客户端发送更新通知。此外,观察者模式还允许动态修改订阅者列表,无论是添加新订阅者还是移除不再感兴趣的订阅者,都可以在不修改服务器代码的情况下轻松实现。这为软件设计带来了极大的灵活性和可扩展性。

模式结构

观察者模式的基本结构图如下所示。
在这里插入图片描述

抽象主题接口 (AbstractSubject) 扮演着主题(数据服务器)的角色,并且它内部维护了一个列表,用于记录所有对其数据感兴趣的订阅者。具体观察者 (客户端)通过调用 subscribe 函数,向数据服务器传递一个指向 accept(Datum) 函数的指针来将自己添加到通知列表中,同样地,客户端也可以通过传递相同的指针调用 unsubscribe 方法来移除自己。

当主题决定通知其客户端有新数据可用时,它会调用 notify() 函数。这个函数会遍历订阅者列表,对每个订阅者调用它们提供的 accept(Datum) 函数,并传递相关的数据。

另一方面,抽象观察者接口 提供了一个 accept(Datum) 函数的实现框架,用于接收和处理从主题 (数据服务器) 传入的数据。具体观察者 类将继承 抽象观察者接口,并实现这个函数以处理特定类型的数据。

在这个结构中,抽象主题接口抽象观察者接口 都是抽象类,它们定义了观察者模式所需的基本接口和行为。具体主题具体观察者类将继承这些抽象类,并实现特定的数据处理和通知逻辑,以满足应用程序的具体需求。这种设计方式提高了代码的模块化和可重用性,使得观察者模式能够在各种不同的上下文中灵活应用。

模式详情

抽象观察者接口

抽象观察者接口抽象主题接口关联 ,在 UML 中,关联表示类与类之间的连接,关联关系使一个类知道另外一个类的属性和方法。这里抽象观察者接口知道抽象主题接口的属性和方法,属于单向关联。这样,抽象观察者接口可以调用抽象主题接口提供的各种服务。

抽象观察者接口包含一个 accept(Datum) 函数声明,用于接收和处理从主题 (数据服务器) 传入的数据。抽象观察者调用 subscribe 函数时,会将 accept 函数作为参数注册到主题的订阅者列表中。当主题有新的数据可用时,会将数据作为参数调用 accept 函数。

每个 具体观察者 负责实现 accept 函数。

除了 accept 函数外,抽象观察者接口还与数据相关联。这通常是通过指针实现的,但也可能是通过变量的形式。“Datum”代表数据的类或结构体。

抽象主题接口

抽象主题接口 在此模式中充当数据服务器的角色,并提供三个核心服务来支持这一模式。

首先,subscribe(acceptPtr) 服务允许 观察者 将自己添加到订阅者列表中。观察者通过传递一个指向 accept 函数的指针来实现这一点。如果成功添加了指向函数的指针,则此服务返回零;如果添加失败(例如,由于指针无效或已存在于列表中),则返回非零值。

其次,unsubscribe(acceptPtr) 服务从订阅者列表中移除指定的观察者。这同样是通过传递指向 accept 函数的指针来完成的。如果成功移除了指针,服务返回零;如果移除失败(例如,由于指针不存在于列表中),则返回非零值。

最后,notify() 函数是抽象主题接口用来通知已订阅观察者数据变化的关键方法。它遍历订阅者列表,并对列表中的每个项调用相应的函数(即之前通过 subscribe 服务添加的指针所指向的函数)。这种机制确保了所有订阅了数据变化的观察者都能及时收到更新通知。

此外,抽象主题接口还持有要传递给观察者的数据(即 Datum)以及订阅者列表(即 NotificationHandle)。在图中,订阅者列表使用指针数组实现,但它也可以是链表或者其它数据结构。

具体观察者

具体观察者 是抽象观察者接口的具体实现。它实现了接口中定义的 acceptPtr(Datum) 函数,该函数用于处理从主题中接收到的数据。通过创建多个具体观察者,我们可以实现多个具有不同处理逻辑的观察者,从而满足不同的业务需求。

具体主题

具体主题 是抽象主题接口的具体实现。除了实现接口中定义的函数外,它还提供了获取和管理要分发给观察者的数据。这意味着具体主题可能需要与数据源进行交互,例如从硬件设备、传感器或其他系统中读取数据,并将其转化为适合观察者处理的形式。这样看来,具体主题还包括 硬件代理 的功能。

数据结构(Datum)

Datum 是观察者模式中传递的数据单位,它包含了具体主题的状态信息或其他相关数据。Datum 的具体形式取决于应用场景和需求,它可以是简单的数据类型(如整数、浮点数等),也可以是复杂的数据结构(如结构体、类对象等)。具体观察者通过订阅主题来接收 Datum,并根据需要对其进行处理。因此,Datum 的设计和定义对于观察者模式的实现和使用至关重要。

效果

观察者模式是一种高效且灵活的设计模式,它大大简化了将数据分发给一组在设计时可能未知的客户端的过程。通过引入订阅机制,观察者模式允许在运行时动态地管理对特定数据感兴趣的客户端列表。这种动态性不仅提供了极高的运行时灵活性,还维护了基本的客户端-服务器关系。

在这种模式中,客户端无需不断地检查数据是否已更新,而是可以在数据实际发生变化时收到通知。最常见的更新策略是在数据发生变化时立即通知客户端,但也可以根据实际需求实现任何适当的策略。这种按需更新的方式显著提高了计算效率,因为客户端只在真正需要的时候进行更新操作。

实现策略

这个模式中唯一复杂的方面是订阅者列表的管理(订阅和解除订阅)。订阅列表的元素几乎总是一个函数回调,即指向具有正确参数的函数的指针。当然,这个参数随着返回的数据而变化。

对于订阅者列表,最简单的方法是声明一个足够大的数组来容纳所有潜在的观察者。这在具有高度动态性和许多潜在观察者的系统中会浪费内存。另一种方法是将系统构建为链表,这将使用动态内存分配。

相关模式

该模式可以与前面介绍的模式自由混合使用,因为它们的关注点是正交的。例如,在嵌入式系统中,向 硬件代理 或 硬件适配器 添加观察者功能是非常常见的。

实例

见原书。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://xiahunao.cn/news/2806246.html

如若内容造成侵权/违法违规/事实不符,请联系瞎胡闹网进行投诉反馈,一经查实,立即删除!

相关文章

【MATLAB】 EWT信号分解+FFT傅里叶频谱变换组合算法

有意向获取代码,请转文末观看代码获取方式~ 展示出图效果 1 EWT分解算法 EWT分解算法是一种基于小波变换的信号分解算法,它可以将信号分解为一系列具有不同频率特性的小波分量。该算法的基本思想是将信号分解为多个不同尺度的小波分量,并对…

【Vue3】学习computed计算属性

💗💗💗欢迎来到我的博客,你将找到有关如何使用技术解决问题的文章,也会找到某个技术的学习路线。无论你是何种职业,我都希望我的博客对你有所帮助。最后不要忘记订阅我的博客以获取最新文章,也欢…

python 与 neo4j 交互(py2neo 使用)

参考自:neo4j的python.py2neo操作入门 官方文档:The Py2neo Handbook — py2neo 2021.1 安装:pip install py2neo -i https://pypi.tuna.tsinghua.edu.cn/simple 1 节点 / 关系 / 属性 / 路径 节点(Node)和关系(relationship)是构成图的基础…

学习python的第6天,痛苦焦虑的开始是期待

小号加了她的网易云音乐小号,成为了她的粉丝之一,收到她的私信回复之后,便又开始期待新的回复了,所以嘛,痛苦总是从开始期待开始的............. 昨天学习了python的逻辑控制之 if 和比较 .__eq__(a) 而且在最后顺带…

Git修改提交的文件的用户名和邮箱

实现效果 提交的测试二,用户名:git1 邮箱:email1,更改成 newGit1、newEmail1 一、概念 Git配置文件级别 系统级、全局级、本地级,生效规则是本地级>全局级>系统级,也就是当本地级配置上此属性,那么…

深度学习中数据的转换

原始(文本、音频、图像、视频、传感器等)数据被转化成结构化且适合机器学习算法或深度学习模型使用的格式。 原始数据转化为结构化且适合机器学习和深度学习模型使用的格式,通常需要经历以下类型的预处理和转换: 文本数据&#xf…

【坑】Spring Boot整合MyBatis,一级缓存失效

一、Spring Boot整合MyBatis,一级缓存失效 1.1、概述 MyBatis一级缓存的作用域是同一个SqlSession,在同一个SqlSession中执行两次相同的查询,第一次执行完毕后,Mybatis会将查询到的数据缓存起来(缓存到内存中&#xf…

B² NETWORK空投

空投要点 众多大机构支持,是为数不多的有 Bitcoin 主网验证 Rollup 解决方案的 BTC Layer2,提前埋伏其实是普通人抢早期筹码最好的方式,参加 B Buzz 就是手握金铲子,对标eth二层网络的繁荣程度你就能想象这个前景明牌空投5%给早期…

2024年 前端JavaScript入门到精通 第四天 笔记

4.1 函数的基本使用以及封装练习 ★ 函数命名规范 4.2 函数的参数以及默认参数 函数的灵魂!!! 4.3 函数封装数组求和案例 4.4 函数返回值return 4.5 函数返回值细节以及上午总结 4.6 函数返回值案例-求最大值和最 4.7 函数复习以及断点进入函…

《TCP/IP详解 卷一》第3章 链路层

目录 3.1 引言 3.2 以太网 3.3 全双工 省点 自动协商 流量控制 3.4 网桥和交换机 3.5 WiFi 3.6 PPP协议 3.6.1 PPP协议流程 3.7 环回 3.8 MTU和路径MTU 3.9 隧道基础 3.9.1 GRE 3.9.2 PPTP 3.9.3 L2TP 3.10 与链路层相关的攻击 3.11 总结 3.1 引言 城域网&…

【视频编码\VVC】环路滤波基础知识

本文为新一代通用视频编码H.266\VVC原理、标准与实现的简化笔记。 定义:在视频编码过程中进行滤波,滤波后的图像用于后续编码。 目的:1、提升编码图像的质量。2、为后续编码图像提供高质量参考,获得更好的预测效果。 VVC中主要…

RabbitMQ的死信队列和延迟队列

文章目录 死信队列如何配置死信队列死信队列的应用场景Spring Boot实现RabbitMQ的死信队列 延迟队列方案优劣:延迟队列的实现有两种方式: 死信队列 1)“死信”是RabbitMQ中的一种消息机制。 2)消息变成死信,可能是由于…

RuntimeError: CUDNN_STATUS_EXECUTION_FAILED

问题描述: 运行代码时候报错: 原因:pytorch与cuda版本不对,需要重新安装。不过我在复现代码的时候一般是要求特定的环境,不然会有其他错误,所以选择其他解决办法。 解决方案: 在train.py开头…

跨界计算与控制,强化显控和UI, 君正MPU再添新旗舰--Ingenic MPU X2600隆重发布

近日,北京君正隆重发布MPU芯片新产品X2600。该产品以商业和工业应用的数个细分领域为重点目标市场,兼顾通用处理器应用需求。无论从CPU结构的设计,还是专门控制器和接口的配备,都体现了北京君正MPU团队“技术路线上追求自主跨界&a…

鸿蒙开发之Profiler性能分析

一、Profiler性能分析器简介 应用或服务的性能较差时,可能表现为响应速度慢、动画播放不流畅、卡顿、崩溃或极其耗电。为了避免出现这些性能问题,需要通过一系列性能分析工具来确定应用或服务对哪方面资源(例如CPU、内存、显卡、网络和设备电池)的使用率比较高。DevEco St…

代码随想录算法训练营day25|216.组合总和III

216.组合总和III 题目链接/文章讲解:代码随想录 视频讲解:和组合问题有啥区别?回溯算法如何剪枝?| LeetCode:216.组合总和III_哔哩哔哩_bilibili 跟77题差不多,要搞清楚k确定了递归的深度 依旧用回溯三部…

Facebook的数字社交使命:连接世界的下一步

在数字化时代,社交媒体已成为人们生活的重要组成部分,而Facebook作为其中最具影响力的平台之一,一直以来都在努力履行着自己的使命——连接世界。然而,随着时代的变迁和技术的发展,Facebook正在不断探索着连接世界的下…

【Logback】Logback 日志框架的架构

目录 1、Logger(记录器) (1)有效级别和级别继承 (2)日志打印和日志筛选 (3)记录器命名 2、Appenders(追加器) 3、Layouts(布局)…

提示工程(Prompt Engineering)、微调(Fine-tuning) 和 嵌入(Embedding)

主要参考资料: 还没搞懂嵌入(Embedding)、微调(Fine-tuning)和提示工程(Prompt Engineering)?: https://blog.csdn.net/DynmicResource/article/details/133638079 B站Up主Nenly同学…

智能高压森林应急消防泵|保障森林安全|深圳恒峰

随着科技的不断发展,我们的生活质量得到了显著提升。在森林保护领域,一项创新技术正在发挥着关键作用——智能高压森林应急消防泵。这种设备不仅提高了灭火效率,更为森林资源的安全保驾护航。 在过去,面对森林火灾,消防…