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 的设计和定义对于观察者模式的实现和使用至关重要。

效果

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

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

实现策略

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

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

相关模式

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

实例

见原书。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

VSCODE include错误 找不到 stdio.h

解决办法: Ctrl Shift P 打开命令面板, 键入 “Select Intellisense Configuration”(下图是因为我在写文章之前已经用过这个命令,所以这个历史记录出现在了第一行) 再选择“Use gcc.exe ”(后面的Foun…

C# If与Switch的区别

在 switch 语句中使用表达式比较时,编译器会生成一个查找表,其中包含所有表达式的值和对应的 case 标签。因此,与使用常量或字面量比较相比,使用表达式比较可能会略微降低性能。 只有当 switch 语句中的所有 case 标签都使用常量或…

Linux快速修改ip地址

Linux修改IP配置 一 、查找ip配置文件 ifcfg-ens33二、编辑 vi ifcfg-ens33文件三、重启网络或者重启系统 一 、查找ip配置文件 ifcfg-ens33 cd /etc/sysconfig/network-scripts/ls //查看network-scripts文件夹下面的文件二、编辑 vi ifcfg-ens33文件 vi ifcfg-ens33注意&…

反序列化 [NPUCTF2020]ReadlezPHP1

打开题目 直接查看源代码 打开源代码发现了个./time.php?source 访问一下 审计代码: 现存在反序列化语句:$ppp unserialize($_GET["data"]);和执行漏洞:echo $b($a); 发现在__destruct()方法里面有 echo $b($a); 这个是php的…

用6点结构标定5点结构的顺序

( A, B )---6*30*2---( 1, 0 )( 0, 1 ) 让网络的输入只有6个节点,AB训练集各由6张二值化的图片组成,A的6张图片共有5个点,B全是0.收敛误差7e-4,收敛199次,统计迭代次数平均值并排序。 如果使行和列自由变换&#xff0…

Sora是什么?

文章目录 前言Sora是什么?功能特色优点 缺点Sora模型的工作原理如何使用Sora模型Sora模型的应用场景Sora模型带来的问题虚假信息版权问题 后记 前言 Sora是美国人工智能研究公司OpenAI发布的一款令人惊叹的人工智能文生成视频大模型。近年来,人工智能技…

《凤凰架构》 -分布式事务章节 读书笔记

分布式事务严谨的定义:分布式环境下的事务处理机制 CAP定理:在一个分布式系统中,涉及共享数据问题时,以下三个特性最多只能同时满足两个 一致性:代表数据在任何时刻、任何分布式节点中看到的都是符合预期的&#xff0…

SpringBoot-2.7.6基于SLF4J日志门面的日志框架切换

SpringBoot 没有强制性的日志记录依赖项,但 Commons Logging API 除外,它通常由 Spring Framework 的模块提供。 要使用 Logback,您需要将其包含在类路径中。 推荐的方法是您只需要通过启动器,这都取决于 . 对于 Web 应用程序 ,因为它可传递地依赖于日志记录启动器。 如果…

持续集成,持续交付和持续部署的概念,以及GitLab CI / CD的介绍

引言:上一期我们部署好了gitlab极狐网页版,今天我们介绍一下GitLabCI / CD 目录 一、为什么要 CI / CD 方法 1、持续集成 2、持续交付 3、持续部署 二、GitLab CI / CD简介 三、GitLab CI / CD 的工作原理 4、基本CI / CD工作流程 5、首次设置 …

ELK入门(三)-Kibana

Kibana Kibana是一个开源的分析与可视化平台,设计出来用于和Elasticsearch一起使用的。你可以用kibana搜索、查看存放在Elasticsearch中的数据。Kibana与Elasticsearch的交互方式是各种不同的图表、表格、地图等,直观的展示数据,从而达到高级…

让C语言代码变抽象(二)

目录 前言: 代码: 前言: 在今天写代码的时候,我又想到一个更抽象的代码。 我在写注释的时候突然想想到条件编译的东西,好像也能用来注释东西。 代码: 我们在这直接上干货 我们知道在条件编译中有一个叫…

鸿蒙开发-DevEcoStudio的安装及使用

DevEcoStudio的安装及使用 前言 DevEcoStudio分为两个版本,分别用于HarmonyOS和OpenHarmony开发,截止文档编写时,用于开发HarmonyOS的DevEcoStudio版本为3.1.1,适用于OpenHarmony的DevEcoStudio版本为4.0.0。 接下来的文档是基…

React18源码: schedule任务调度messageChannel

React调度原理(scheduler) 在React运行时中,调度中心(位于scheduler包)是整个React运行时的中枢(其实是心脏),所以理解了scheduler调度,就基本掌握了React的核心React两大循环:从宏…

年关将至送大礼 社区适时献爱心

在这个快节奏的时代,社区作为人们生活的重要组成部分,其凝聚力和互助精神显得尤为重要。2024年2月7日,实践队员李若钰有幸参与了社区礼盒分装的活动,这不仅仅是一次简单的劳动,更是一次心灵的洗礼和感悟。 礼盒分装&am…

HTML5和CSS3提高

一、HTML5的新特性 增加了一些新的标签,新的表单,新的表单属性,IE9以上版本的浏览器才支持 注意: 这些语义化标准主要针对搜索引擎的 新标签可以使用多次 在IE9中需要把这些元素转化为块级元素 新增的多媒体标签 主要包含两个…

Java JDBC:林浩然与杨凌芸的编程奇缘

Java JDBC:林浩然与杨凌芸的编程奇缘 Java JDBC: The Programming Odyssey of Lin Haoran and Yang Lingyun 在那个充满二进制和算法符号的世界里,我们的男主角林浩然,一个热爱Java的码农新秀,正准备踏上他的JDBC探险之旅。他那双…

【vscode】按F5无法执行调试python或go

原因: 找不到解析器,需要安装插件(python,或go 等) 安装插件后,还是无法执行,按 ctrlshiftp,看不到解析器 正常应该是: 解决方法: 1、判断python是否安装成功 pyth…

Selenium(简单入门)

请直接看原文:selenium 使用教程详解-java版本 - 小葛师兄 - 博客园 (cnblogs.com) -------------------------------------------------------------------------------------------------------------------------------- 第一章 Selenium 概述# 1.1.Selenium 发展史# ​ …

“从根到叶:深入理解排序数据结构“

一.排序的概念及引用 1.1排序的概念 排序是指将一组数据按照一定的规则重新排列的过程。排序的目的是为了使数据具有有序性,便于查找、插入、删除等操作,提高数据的组织和管理效率。 稳定性是指如果序列中存在相等元素,在排序完成后&#…