【设计模式】 观察者模式介绍及C代码实现
【设计模式】 观察者模式介绍及C代码实现
背景
在软件构建过程中,我们需要为某些对象建立一种“通知依赖关系”,即一个对象(目标对象)的状态发生改变,所有的依赖对象(观察者对象)都将得到通知。如果这样的依赖关系过于紧密,将使软件不能很好地抵御变化。
假设有一个简单的应用场景,一个气象站记录当地天气的温度、湿度和气压,并将这些数据展示在一个显示屏上。现在需要实现一个气象站的应用,支持多个显示屏同时显示气象数据,这时候就可以使用观察者模式来实现。
定义
观察者模式(Observer Pattern)是一种常用的设计模式,它定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象,当主题对象发生变化时,它的所有观察者都会收到通知并更新自己的状态。观察者模式又称为发布-订阅模式。
观察者模式的主要角色包括以下几个部分:
Subject(主题):被观察的对象,它将所有观察者对象的引用保存在一个集合中,并提供了添加和删除观察者对象的方法。
Observer(观察者):观察者接口,定义了更新自己的状态的方法,以便主题在状态发生变化时通知观察者。
ConcreteSubject(具体主题):具体的主题实现类,维护一个状态,并在状态发生变化时通知所有观察者。
ConcreteObserver(具体观察者):具体的观察者实现类,实现观察者接口中定义的方法,以便在接收到主题的通知时更新自己的状态。
应用场景
观察者模式常常被用于以下场景:
-
一对多的依赖关系:当一个对象的状态发生变化时,需要通知多个对象,并且这些对象需要根据主题对象的状态进行相应的处理,这时可以使用观察者模式。
-
发布-订阅模型:观察者模式也被称为发布-订阅模型。在这个模型中,主题对象充当发布者的角色,而观察者充当订阅者的角色。主题对象不需要知道具体的观察者,只需要通知所有的观察者即可。
-
GUI 事件处理:在 GUI 编程中,经常使用观察者模式来处理用户界面事件。例如,当用户点击按钮时,程序会通知所有的事件监听器来处理该事件。
-
网络编程:在网络编程中,经常使用观察者模式来实现异步通信。例如,当客户端连接服务器时,服务器会通知所有的客户端连接已建立。
总之,当一个对象的状态发生变化时,需要通知多个对象,并且这些对象需要根据主题对象的状态进行相应的处理时,可以使用观察者模式。观察者模式可以帮助我们降低系统的耦合度,增强系统的灵活性和可维护性。
模式结构

实现步骤
观察者模式的实现步骤如下:
-
定义 Subject 接口,该接口定义了注册观察者、删除观察者和通知观察者的方法。
-
定义 Observer 接口,该接口定义了观察者需要实现的方法。
-
定义具体主题类 ConcreteSubject,实现 Subject 接口,并包含观察者列表。具体主题类负责管理观察者列表,并在状态发生改变时通知观察者。
-
定义具体观察者类 ConcreteObserver,实现 Observer 接口,并在 update 方法中更新自己的状态。
-
在具体主题类中实现注册观察者、删除观察者和通知观察者的方法。
-
在具体观察者类中实现 update 方法,当主题对象的状态发生改变时,观察者会接收到通知并更新自己的状态。
-
在客户端代码中创建具体主题对象和具体观察者对象,并将观察者注册到主题对象中。
-
当主题对象的状态发生改变时,观察者会接收到通知并更新自己的状态,从而实现观察者模式的功能。
观察者模式的核心思想是将主题和观察者解耦,使得它们可以独立地变化。主题对象负责管理状态和通知观察者,而观察者对象负责更新自己的状态。观察者模式可以帮助我们降低系统的耦合度,增强系统的灵活性和可维护性。
C语言代码示例
#include <stdio.h>
#include <stdlib.h>
#include <string.h>// 定义观察者接口
typedef struct _Observer {void (*update)(struct _Observer* self);
} Observer;// 定义主题接口
typedef struct _Subject {void (*registerObserver)(struct _Subject* self, Observer* observer);void (*removeObserver)(struct _Subject* self, Observer* observer);void (*notifyObservers)(struct _Subject* self);
} Subject;// 定义具体观察者类
typedef struct _ConcreteObserver {Observer base;char name[20];
} ConcreteObserver;// 定义具体主题类
typedef struct _ConcreteSubject {Subject base;Observer* observers[10];int count;int state;
} ConcreteSubject;// 实现具体观察者类的更新方法
void ConcreteObserver_update(Observer* self) {ConcreteObserver* observer = (ConcreteObserver*)self;printf("%s: state changed\n", observer->name);
}// 实现具体主题类的注册观察者方法
void ConcreteSubject_registerObserver(Subject* self, Observer* observer) {ConcreteSubject* subject = (ConcreteSubject*)self;if (subject->count < 10) {subject->observers[subject->count++] = observer;}
}// 实现具体主题类的删除观察者方法
void ConcreteSubject_removeObserver(Subject* self, Observer* observer) {ConcreteSubject* subject = (ConcreteSubject*)self;for (int i = 0; i < subject->count; i++) {if (subject->observers[i] == observer) {for (int j = i; j < subject->count - 1; j++) {subject->observers[j] = subject->observers[j + 1];}subject->count--;break;}}
}// 实现具体主题类的通知观察者方法
void ConcreteSubject_notifyObservers(Subject* self) {ConcreteSubject* subject = (ConcreteSubject*)self;for (int i = 0; i < subject->count; i++) {subject->observers[i]->update(subject->observers[i]);}
}int main() {// 创建具体主题对象ConcreteSubject subject;memset(&subject, 0, sizeof(ConcreteSubject));subject.base.registerObserver = ConcreteSubject_registerObserver;subject.base.removeObserver = ConcreteSubject_removeObserver;subject.base.notifyObservers = ConcreteSubject_notifyObservers;// 创建具体观察者对象ConcreteObserver observer1, observer2;memset(&observer1, 0, sizeof(ConcreteObserver));memset(&observer2, 0, sizeof(ConcreteObserver));observer1.base.update = ConcreteObserver_update;observer2.base.update = ConcreteObserver_update;strcpy(observer1.name, "Observer 1");strcpy(observer2.name, "Observer 2");// 注册观察者subject.base.registerObserver(&subject.base, (Observer*)&observer1);subject.base.registerObserver(&subject.base, (Observer*)&observer2);// 改变主题对象的状态subject.state = 1;// 通知观察者subject.base.notifyObservers(&subject.base);// 删除观察者
总结
-
使用面向对象的抽象, Observer模式使得我们可以独立地改变目标与观察者,从而使二者之间的依赖关系达致松耦合。
-
目标发送通知时,无需指定观察者,通知(可以携带通知信息作为参数)会自动传播。
-
观察者自己决定是否需要订阅通知,目标对象对此一无所知。
-
观察者模式是基于事件的UI框架中非常常用的设计模式,也是MVC模式的一个重要组成部分。
相关文章:
【设计模式】 观察者模式介绍及C代码实现
【设计模式】 观察者模式介绍及C代码实现 背景 在软件构建过程中,我们需要为某些对象建立一种“通知依赖关系”,即一个对象(目标对象)的状态发生改变,所有的依赖对象(观察者对象)都将得到通知。…...
01-Maven基础-简介安装、基本使用(命令)、IDEA配置、(写jar,刷新自动下载)、依赖管理
文章目录0、Maven1、Maven 简介2、Maven 安装配置安装配置步骤3、Maven 基本使用Maven 常用命令Maven 生命周期IDEA 配置 MavenMaven 坐标详解IDEA 创建 Maven 项目IDEA 导入 Maven 项目配置 Maven-Helper 插件 (非常实用的小插件)依赖管理使用坐标导入 jar 包依赖范围0、Maven…...
一、前端稳定性规约该如何制定
前言 稳定性是数学或工程上的用语,判别一系统在有界的输入是否也产生有界的输出。若是,称系统为稳定;若否,则称系统为不稳定。 前端稳定性的体系建设大约可以分为了发布前,发布后,以及事故解决后三个阶段…...
Docker(三)Docker网络
目录1 结论知识2 link3 自定义网络1 结论知识 每一个容器启动时都会被分配一个ip地址;宿主机可以ping通任何一个docker容器;启动docker之后,宿主机默认网卡docker0,启动容器在宿主机注册网卡,使用的evth-pair技术&…...
Js高级API
Decorator装饰器 针对属性 / 方法的装饰器 // decorator 外部可以包装一个函数,函数可以带参数function Decorator (type) {/*** 这里是真正的decorator* description: 装饰的对象的描述对象* target:装饰的属性所述类的原型,不是实例后的类。如果装饰…...
团队:在人身上,你到底愿意花多大精力?
你好,我是叶芊。 今天我们讨论怎么带团队这个话题,哎先别急着走,你可能跟很多人一样,觉得带团队离我还太远,或者觉得我才不要做管理,我要一路技术走到底,但是你知道吗?带团队做事&am…...
Linux-Poolkit提权
Linux-Poolkit提权 漏洞复现- Linux Polkit 权限提升漏洞(CVE-2021-4034) 0x00 前言 polkit是一个授权管理器,其系统架构由授权和身份验证代理组成,pkexec是其中polkit的其中一个工具,他的作用有点类似于sudo&#x…...
【React全家桶】React Hooks
React Hookshooks介绍useState(保存组件状态)useEffect()useCallback(记忆函数)useMemo() 记忆组件useRef(保存引用值)useReducer()useContext(减少组件层级)自定义hookshooks介绍 在react类组件(class)写法中,有setState和生命周期对状态进…...
CLIP论文阅读
Learning Transferable Visual Models From Natural Language Supervision 利用自然语言的监督信号学习可迁移的视觉模型 概述 迁移学习方式就是先在一个较大规模的数据集如ImageNet上预训练,然后在具体的下游任务上再进行微调。这里的预训练是基于有监督训练的&am…...
华为OD机试真题Python实现【身高排序】真题+解题思路+代码(20222023)
身高排序 题目 小明今年升学到了小学一年级, 来到新班级后,发现其他小朋友身高参差不齐, 然后就想基于各小朋友和自己的身高差,对他们进行排序, 请帮他实现排序 🔥🔥🔥🔥🔥👉👉👉👉👉👉 华为OD机试(Python)真题目录汇总 输入 第一行为正整数H…...
Spring Cache的使用--快速上手篇
系列文章目录 分页查询–Java项目实战篇 全局异常处理–Java实战项目篇 完善登录功能–过滤器的使用 更多该系列文章请查看我的主页哦 文章目录系列文章目录前言一、Spring Cache介绍二、Spring Cache的使用1. 导入依赖2. 配置信息3. 在启动类上添加注解4. 添加注解4.1 CacheP…...
(三十八)MySQL是如何支持4种事务隔离级别的?Spring事务注解是如何设置的?
上次我们讲完了SQL标准下的4种事务隔离级别,平时比较多用的就是RC和RR两种级别,那么在MySQL中也是支持那4种隔离级别的,基本的语义都是差不多的 但是要注意的一点是,MySQL默认设置的事务隔离级别,都是RR级别的&#x…...
【博学谷学习记录】大数据课程-学习第八周总结
Hadoop初体验 使用HDFS 1.从Linux本地上传一个文本文件到hdfs的/目录下 #在/export/data/目录中创建a.txt文件,并写入数据 cd /export/data/ touch a.txt echo "hello" > a.txt #将a.txt上传到HDFS的根目录 hadoop fs -put a.txt /2.通过页面查看…...
go cobra初试
cobra开源地址 https://github.com/spf13/cobra cobra是什么 Cobra is a library for creating powerful modern CLI applications. Cobra is used in many Go projects such as Kubernetes, Hugo, and GitHub CLI to name a few. This list contains a more extensive lis…...
【react全家桶】 事件处理
文章目录03 【事件处理】1.React事件2.类式组件绑定事件3.向事件处理程序传递参数4.收集表单数据5.受控和非受控组件5.函数的柯里化03 【事件处理】 React的事件是通过onXxx属性指定事件处理函数 React 使用的是自定义事件,而不是原生的 DOM 事件 React 的事件是通过…...
RabbitMQ交换机(Exchanges)
目录 一、概念 二、临时队列 三、绑定 四、Fanout(扇出交换机) (一)介绍 (二)实战 五、Direct(直接交换机) (一)介绍 (二)实…...
2023年java初级面试题10道基础试水题
1、面向对象的特征有哪些方面?答:面向对象的特征主要有以下几个方面:1)抽象:抽象是将一类对象的共同特征总结出来构造类的过程,包括数据抽象和行为抽象两方面。抽象只关注对象有哪些属性和行为,并不关注这些行为的细节…...
烙铁使用方法
烙铁使用 烙铁是硬件工程师最经常使用的工具之一,一把性能保持良好的烙铁能帮助我们快速进行电路调试。烙铁第一次加热时采用焊锡均匀涂覆在烙铁头上,以便去除包在烙铁头上面的氧化物。在工作中我们需要根据情况选择合适的烙铁头类型,合适的温度进行操作。完成焊接后要在烙铁…...
golang日期转换、日期增减计算、时间戳转换
// 固定日期格式format : "2006-01-02 15:04:05"// 按本地时区解析日期location, _ : time.ParseInLocation(format, "2022-02-20 11:30:00", time.Local)// 增加1年,三个参数分别是:年,月,日date : location…...
Android 多种支付方式的优雅实现
场景App 的支付流程,添加多种支付方式,不同的支付方式,对应的操作不一样,有的会跳转到一个新的webview,有的会调用系统浏览器,有的会进去一个新的表单页面,等等。并且可以添加的支付方式也是不确…...
终极指南:3分钟掌握ControlNet-v1-1_fp16_safetensors高效AI图像控制
终极指南:3分钟掌握ControlNet-v1-1_fp16_safetensors高效AI图像控制 【免费下载链接】ControlNet-v1-1_fp16_safetensors 项目地址: https://ai.gitcode.com/hf_mirrors/comfyanonymous/ControlNet-v1-1_fp16_safetensors ControlNet-v1-1_fp16_safetensor…...
嵌入式开发代码版本比较工具与技巧
1. 嵌入式开发中的代码版本差异查看方法在嵌入式开发过程中,代码版本管理是每个工程师必须掌握的核心技能。随着项目迭代和功能更新,我们经常需要比较不同版本代码之间的差异,无论是为了代码审查、问题排查还是版本合并。作为一名嵌入式开发者…...
西门子博图V16实战:5种工作模式机械手PLC程序全解析(附HMI组态文件)
西门子博图V16实战:5种工作模式机械手PLC程序全解析(附HMI组态文件) 在工业自动化领域,机械手控制系统一直是核心难点之一。如何实现多工作模式的灵活切换、确保信号互锁安全可靠,是每个PLC程序员必须掌握的技能。本文…...
RK3588上OpenCV+GStreamer播放RTSP卡成PPT?一个环境变量让帧率从7飙升到25+
RK3588视频开发实战:OpenCVGStreamer硬解码性能翻倍秘籍 在嵌入式视觉应用开发中,RK3588凭借其强大的多媒体处理能力成为众多开发者的首选平台。但当你在Python环境中使用OpenCV配合GStreamer进行RTSP视频流处理时,是否遇到过这样的尴尬&…...
基于Flowable全局监听器实现智能节点跳过:告别重复审批
1. 为什么需要智能跳过重复审批节点? 想象一下这样的场景:你设计了一个采购审批流程,部门经理需要先后审批"采购申请"和"采购确认"两个节点。但当这两个节点都分配给同一位经理时,他会在系统里看到两个完全相…...
微信小程序自动化测试:自定义测试(Minium)
🍅 点击文末小卡片 ,免费获取软件测试全套资料,资料在手,涨薪更快录制回放支持输入,文本查找,断言等自动化测试基础操作,无需编写代码,用例生成效率高,但是部分操作不支持…...
百考通:AI全流程智能化赋能,让每一份调研与设计都高效落地
在数字化时代,市场调研、产品设计、学术研究等场景中,问卷设计作为核心环节,直接影响着数据收集的质量与工作推进的效率。传统问卷设计往往面临流程繁琐、耗时耗力、问题设计不精准等痛点,而百考通(https://www.baikao…...
从脑电波到股票K线:EMD经验模态分解在5个真实场景下的避坑指南
从脑电波到股票K线:EMD经验模态分解在5个真实场景下的避坑指南 当你第一次看到脑电波信号与股票K线图被放在同一个分析框架下讨论时,可能会觉得这是两个毫不相关的领域。但事实上,无论是神经科学家的EEG数据,还是量化交易员的股价…...
软考:团队管理与绩效域50大实战难题破解清单,写进论文直接加分!
对于软考高项(信息系统项目管理师)的考生来说,论文是决定成败的关键。而一篇高分论文的核心,在于能否用真实、具体的项目实践,去论证你对项目管理知识体系的深刻理解。项目团队管理和项目绩效域是论文中最常考、也最容…...
LVM命令大全
以下是 Linux LVM(逻辑卷管理)的核心命令分类详解及常用操作示例,结合最新技术网页整理而成:一、物理卷(PV)管理命令功能关键参数示例pvcreate初始化物理设备为PV-f(强制)-u…...
