概述

在没有面向对象语法的C语言中,策略(Strategy)模式和状态(State)模式都通过“上下文 + 接口”组合来模拟多态。
它们在代码结构上几乎一致,但设计意图和应用场景却差异很大。

本文分三部分深入剖析:

  1. 在C语言中如何模拟多态
  2. 策略模式与状态模式的设计意图与差异
  3. 结合EEPROM抽象层案例,展示二者协作与状态机应用

1. C语言中的“模拟多态”

在C语言中,多态可通过结构体与函数指针模拟:上下文(Context)持有一组函数指针(vtable),动态指向具体实现。下例展示通用框架:

// 通用接口类型(vtable)
typedef struct {void (*action)(void *ctx, int evt);
} module_iface_t;// 通用上下文
typedef struct {const module_iface_t *iface;  // 指向当前策略或状态的 vtablevoid *instance_data;          // 指向具体策略/状态实例私有数据
} module_ctx_t;

在策略模式中,action 对应具体算法;在状态模式中,action 则为事件处理函数。此框架既可用于策略,也可用于状态,赋予系统极高的复用性。

2. 策略模式 vs. 状态模式

2.1 策略模式:关注“如何做”

  • 目的:封装一组可互换的算法,让调用者在运行时自行选择
  • 决策权:在外部,上下文被动执行指定策略
  • 应用场景:滤波算法切换、通信协议选择、后端存储后备机制等

示例类图

sensor_t
+filter_strategy_t *strat
+process()
«interface»
filter_strategy_t
+filter(buf, len)
mean_filter
kalman_filter

2.2 状态模式:关注“何时做、做什么”

  • 目的:将对象行为与内部状态绑定,使行为随状态自动切换
  • 决策权:在内部,状态自身决定是否及何时转换
  • 应用场景:设备驱动、协议状态机、时序复杂的后端服务等

示例类图与状态图

usb_dev_t
+usb_state_t *state
+handle_event(evt)
«interface»
usb_state_t
+handle(dev, evt)
st_disconnected
st_connecting
st_ready
st_error
EVT_PLUG_IN
EVT_ENUM_OK
EVT_ENUM_FAIL
EVT_PLUG_OUT
EVT_PLUG_OUT
Disconnected
Connecting
Ready
Error

核心差异

  • 策略模式:算法平行,可在任意时刻替换
  • 状态模式:状态有序,转换逻辑由状态机管理

2.3 小结:模式赋能架构

在前文中,我们分别阐述了策略模式如何将“如何做”的决策权交给外部,做到算法与调用逻辑的彻底解耦;以及状态模式如何把“何时做、做什么”的流程控制封装到内部状态机中,保障行为随状态演进而自动切换。

既然二者在实现上高度相似,我们有时候可以将二者组合起来使用。

3. 实战:高可靠EEPROM抽象层

下面通过分层设计,展示策略模式与状态机的协同应用。

3.1 统一层:策略模式应用

需求

为应用暴露统一接口,支持I2C EEPROM与NOR Flash两种后端,具备自动检测与运行时降级能力。

设计
  • 上下文unified_eeprom
  • 策略接口:包含 read_var, write_var 等函数指针
  • 具体策略I2C_EEPROM_ServiceEE_NOR_Flash_Service
  • 决策枚举eeprom_backend_t { AUTO, I2C, FLASH, HYBRID }
// 策略接口定义
typedef struct {rt_err_t (*read_var)(int id, void *buf, size_t len);rt_err_t (*write_var)(int id, const void *buf, size_t len);
} eeprom_iface_t;// 统一上下文
typedef struct {const eeprom_iface_t *iface;eeprom_backend_t current_backend;
} unified_eeprom_t;
运行时降级
eeprom_set_backend(EEPROM_BACKEND_AUTO);rt_err_t rc = eeprom_read_var(VAR_ID_X, buf, len);
if (rc != RT_EOK) {// 失败后自动切换策略eeprom_set_backend(EEPROM_BACKEND_FLASH);rc = eeprom_read_var(VAR_ID_X, buf, len);
}

AUTO模式下,统一层在I2C失败时自动切换至Flash。此决策过程对上层透明,上层仅感知最终结果。

3.2 后端服务:HSM驱动的状态模式

将I2C和Flash服务均设计为分层状态机,封装所有时序逻辑,实现可恢复与高可靠。

3.2.1 I2C EEPROM状态机
  • 关键状态UNINITIALIZEDIDLEREADINGWRITINGWAITING_READYVERIFYINGERROR
  • 核心逻辑:跨页读取、分页写入+轮询就绪、可选校验与指数退避重试
init_ok
EVT_READ_REQ
EVT_WRITE_REQ
page_done
ready
timeout
verify_ok
verify_fail
read_done
UNINITIALIZED
IDLE
READING
WRITING
WAITING_READY
VERIFYING
ERROR
3.2.2 NOR Flash状态机
  • 关键状态UNINITIALIZEDVALID_PAGEMIGRATINGFORMATTINGERROR
  • 核心逻辑:双页交替写、数据迁移、启动恢复与磨损均衡
init_ok
page_full
migration_done
write_fail
recoverable
format_done
UNINITIALIZED
VALID_PAGE
MIGRATING
ERROR
FORMATTING

4. 协同:策略 × 状态机

在“高可靠EEPROM抽象层”案例中,我们已经看到了策略模式与状态机的协同:

  • 策略层 (统一层):决定“走哪条路径”,即选择I2C后端还是Flash后端。
  • 状态机层 (后端服务):负责“路径内部的时序与恢复”,如I2C的写后轮询、Flash的页面迁移。

两者协同时,可在复杂约束下实现透明降级与高可用。

Appunified_eeprom(Strategy)I2C_Service(HSM)Flash_Service(HSM)eeprom_read_var(id, buf, len)策略=I2C优先ee_read_var(...)RT_ERROR触发降级策略fee_read_var(...)RT_EOKRT_EOKAppunified_eeprom(Strategy)I2C_Service(HSM)Flash_Service(HSM)

这种“策略包含状态机”的组合非常强大,下面我们深入探讨其两种主要协同模式。

4.1 模式一:将“完整状态机”作为策略单元

这是EEPROM案例的核心架构模式。我们把一个“完整的状态机(包含其所有状态、转移逻辑和动作)”封装成一个策略实现。Context仅需持有指向不同状态机策略的指针,即可在运行时“一键切换整体行为”。

这相当于将I2C_EEPROM_ServiceEE_NOR_Flash_Service这两个复杂的状态机,包装成符合eeprom_iface_t接口的策略单元。

unified_eeprom_t
+eeprom_iface_t *iface
+set_backend()
+read_var()
«interface»
eeprom_iface_t
+read_var()
+write_var()
I2C_EEPROM_Service
-hsm_t i2c_hsm
+read_var()
+write_var()
Flash_EEPROM_Service
-hsm_t flash_hsm
+read_var()
+write_var()
后端服务 (状态机实现)
统一层 (策略选择)
选择策略
降级切换
执行具体时序
执行具体时序
I2C HSM
Flash HSM
I2C Service Strategy
unified_eeprom
Flash Service Strategy

适用场景

  • 复杂流程的替换:将“I2C读写流程”与“Flash读写流程”抽象为可互换的策略。
  • 行为隔离:两套状态机逻辑完全独立,互不干扰,便于独立开发与测试。

4.2 模式二:在“状态内部”嵌入子策略

当状态机的主流程固定,但某个具体状态的“动作”或“算法”需要灵活替换时,可以在该状态内部嵌入一个“子策略”。

例如,在Flash_EEPROM_ServiceMIGRATING状态中,数据校验可以是一个子策略。我们可以根据系统要求,在“快速校验(CRC8)”和“高可靠校验(CRC32)”之间切换,而无需改变状态机的主体结构。

// MIGRATING状态的动作函数
void on_migrating_entry(hsm_t *sm, const hsm_event_t *event) {// ...// 根据运行时配置选择校验策略if (is_fast_mode()) {ctx->crc_strategy = &crc8_strategy;} else {ctx->crc_strategy = &crc32_strategy;}// ...
}// 在迁移过程中使用子策略
void perform_migration_step(context_t *ctx) {// ...uint32_t crc = ctx->crc_strategy->calculate(data, len);// ...
}

适用场景

  • 算法替换:状态机结构稳定,但内部算法需要动态调整(如加密、压缩、校验算法)。
  • 功能开关:在不改变状态流程的情况下,通过空策略(noop)实现功能的动态开启或关闭。

4.3 C代码示例:用策略管理多状态机

下面的代码演示了“模式一”的通用实现,即如何通过策略模式来管理和切换两个完全独立的状态机流程。

// "状态机策略"的统一接口
typedef struct sm_ctx_s sm_ctx_t;
typedef struct {void (*init)(sm_ctx_t*);void (*dispatch)(sm_ctx_t*, int event);
} sm_strategy_t;// 上下文,持有当前的状态机策略
struct sm_ctx_s {const sm_strategy_t *ops;void *state_data; // 指向具体状态机的私有数据
};// --- 状态机A (例如: I2C EEPROM) ---
void sma_init(sm_ctx_t *c){ /* ... 状态机A初始化 ... */ }
void sma_dispatch(sm_ctx_t *c, int e){ /* ... A的事件处理 ... */ }
const sm_strategy_t smA_strategy = { sma_init, sma_dispatch };// --- 状态机B (例如: Flash EEPROM) ---
void smb_init(sm_ctx_t *c){ /* ... 状态机B初始化 ... */ }
void smb_dispatch(sm_ctx_t *c, int e){ /* ... B的事件处理 ... */ }
const sm_strategy_t smB_strategy = { smb_init, smb_dispatch };// 应用层通过策略接口与状态机交互
void application_logic(void) {sm_ctx_t fsm;// 根据条件选择一个完整的状态机流程作为当前策略if (use_i2c_backend()) {fsm.ops = &smA_strategy;} else {fsm.ops = &smB_strategy;}// 后续操作对具体状态机无感知fsm.ops->init(&fsm);while (has_event()) {fsm.ops->dispatch(&fsm, next_event());}
}

实践要点(结合统一EEPROM案例)

  • 策略层unified_eeprom负责选择 I2C 或 Flash 后端(含 AUTO/降级),对上层透明。
  • 状态机层i2c_eeprom_serviceee_nor_flash_service各自封装页写、迁移、恢复等复杂时序与错误处理。
  • 并发与锁序:策略层持短锁做选择,状态机在各自内部串行化操作,避免跨层嵌套锁。
  • 失败处理:策略层负责统计健康度并决策是否切换;状态机负责局部的、可恢复的重试。

5. 小结

  • 在嵌入式C中,可通过“上下文 + 接口”框架复用策略与状态模式实现结构。
  • 策略模式关注“如何做”,决策权在外部;状态模式关注“何时做、做什么”,决策权在内部。
  • 将策略与状态机结合,可构建透明、可降级、高可用的分层架构。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/diannao/95105.shtml
繁体地址,请注明出处:http://hk.pswp.cn/diannao/95105.shtml
英文地址,请注明出处:http://en.pswp.cn/diannao/95105.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

人工智能、机器学习、深度学习:2025技术革命的深度解析

目录 人工智能、机器学习、深度学习:技术革命的深度解析 引言 第一部分:人工智能的起源与演进 1.1 人工智能的定义 1.2 人工智能的历史 1.3 人工智能的关键概念 a.知识表示(Knowledge Representation) b.搜索算法&#xf…

【Python】常用内置模块

1.os 文件目录 import os# 创建文件夹 os.mkdir(dir) # 判断文件是否存在 os.path.exists(path) # 列出文件夹下文件列表 os.listdir(dir)""" 常用 """ # 当前文件相对路径 os.getcwd()# 当前文件绝对路径 os.path.abspath(__file__)# 当前文…

(Python)爬虫进阶(Python爬虫教程)(CSS选择器)

源代码:#导入库 import requests from bs4 import BeautifulSoup import pandas as pd#爬虫函数 def scrape_books():#1.基本网址连接base_url "http://books.toscrape.com"#2.获取基本网址responserequests.get(base_url)#3.检查是否正常访问if respons…

第七节 自然语言处理与Bert

自然语言处理与BERT模型:从基础到实践入门 自然语言处理(NLP)的核心目标之一是让计算机理解人类语言的语义和上下文。本文将从基础的字词表示出发,逐步解析传统模型的局限性、Self-attention的突破性思想,以及BERT如何…

攻击者瞄准加密技术的基础:智能合约

虽然利用许多智能合约中的安全漏洞已经成为网络攻击者的长期目标,但越来越多的安全公司开始关注使用欺诈性或混淆的智能合约从加密货币账户中窃取资金的骗局。 根据网络安全公司 SentinelOne 本周发布的分析报告,在最近一次引人注目的攻击中&#xff0c…

基于开源AI大模型、AI智能名片与S2B2C商城小程序的零售智能化升级路径研究

摘要:在零售业数字化转型浪潮中,人工智能技术正从“辅助工具”向“核心生产力”演进。本文聚焦开源AI大模型、AI智能名片与S2B2C商城小程序的协同应用,提出“数据感知-关系重构-生态协同”的三维创新框架。通过分析智能传感、动态画像与供应链…

机器学习 朴素贝叶斯

目录 一.什么是朴素贝叶斯 1.1 从 “概率” 到 “分类” 二.朴素贝叶斯的数学基础:贝叶斯定理 2.1 贝叶斯定理公式 2.2 从贝叶斯定理到朴素贝叶斯分类 2.3 “朴素” 的关键:特征独立性假设 三、朴素贝叶斯的三种常见类型 3.1 高斯朴素贝叶斯&…

A Logical Calculus of the Ideas Immanent in Nervous Activity(神经网络早期的M-P模型)

哈喽,各位朋友大家上午好!今天我们要一起啃下这篇神经科学与逻辑学交叉领域的奠基之作——McCulloch和Pitts的《A Logical Calculus of the Ideas Immanent in Nervous Activity》。这篇论文篇幅不长,但每一个定理、每一个假设都像精密齿轮&a…

大语言模型提示工程与应用:提示工程-提升模型准确性与减少偏见的方法

语言模型可靠性优化 学习目标 在本课程中,我们将学习通过提示工程提升模型事实准确性、减少偏见的有效方法。 相关知识点 语言模型可靠性优化 学习内容 1 语言模型可靠性优化 1.1 事实准确性增强 LLM可能生成看似合理但实际虚构的内容。优化策略包括&#x…

遇到前端导出 Excel 文件出现乱码或文件损坏的问题

1. 检查后端返回的数据格式确认接口响应:确保后端返回的是二进制流(如 ArrayBuffer)或 Base64 编码的 Excel 文件,而非 JSON 字符串。用浏览器开发者工具(Network 标签)检查接口响应类型:正确的…

2025年Cloudflare WAF防护机制深度剖析:5秒盾绕过完全指南

2025年Cloudflare WAF防护机制深度剖析:5秒盾绕过完全指南 技术概述 Cloudflare作为全球领先的CDN和网络安全服务提供商,其WAF(Web Application Firewall)防护系统已经成为现代Web安全的标杆。特别是其标志性的"5秒盾"…

【Android调用相册、拍照、录像】等功能的封装

关于调用Android项目 关于Android中调用相机拍照、录像,调用相册选图等是比较繁琐的,为了减少代码冗余,肯定需要封装成工具类,最终使用大概如下,大部分代码使用Java编写,因为需要照顾到不适用kotlin的伸手…

Git 分支管理:从新开发分支迁移为主分支的完整指南

问题背景 我在使用 Git 进行开发时,由于原有的主分支遭到了污染,不得已在多方尝试之后,决定替换原有的主分支。创建一个新分支并完成了重要修改: 基于提交 0fcb6df0f5e8caa3d853bb1f43f23cfe6d269b18 创建了 new-development 分支…

nginx常见问题(四):端口无权限

当 Nginx 日志报错 bind() to 80 failed (13: Permission denied) 时,这通常是由于权限不足导致 Nginx 无法绑定到 80 端口(该端口为系统特权端口)。以下是详细的问题分析与解决方案:一、问题原因分析80 端口属于 系统特权端口&am…

【线性代数】线性方程组与矩阵——(3)线性方程组解的结构

上一节:【线性代数】线性方程组与矩阵——(2)矩阵与线性方程组的解 总目录:【线性代数】目录 文章目录9. 向量组的线性相关性与线性方程组解的结构9.1. 向量组及其线性组合9.2. 向量组的线性相关性9.3. 向量组的秩9.4. 线性方程组…

机器学习-----K-means算法介绍

一、为什么需要 K-Means?在监督学习中,我们总把数据写成 (x, y),让模型学习 x → y 的映射。 但现实中很多数据根本没有标签 y,例如:啤酒:热量、钠含量、酒精度、价格用户:访问时长、点击次数、…

Spring Security自动处理/login请求,后端控制层没有 @PostMapping(“/login“) 这样的 Controller 方法

一:前言 (1)Spring Security概念: Spring Security 是属于 Spring 生态下一个功能强大且高度可定制的认证和授权框架,它不仅限于 Web 应用程序的安全性,也可以用于保护任何类型的应用程序。 &#xff08…

idea开发工具中git如何忽略编译文件build、gradle的文件?

idea开发工具中: git显示下面这个文件有变更: ~/Documents/wwwroot-dev/wlxl-backend/java/hyh-apis/hyh-apis-springboot/build/resources/main/mapping/AccountRealnameMapper.xml 我git的根路径是: ~/Documents/wwwroot-dev/wlxl-backend/…

状态机浅析

状态机是处理状态依赖型行为的高效工具,通过结构化建模状态转换,解决了传统条件判断的冗余和混乱问题。它在设备控制、流程管理、协议解析等场景中表现优异,核心优势在于逻辑清晰、可扩展性强和易于调试。 一、介绍 1. 概念 状态机&#x…

Windows 手动病毒排查指南:不依赖杀毒软件的系统安全防护

Windows 手动病毒排查指南:不依赖杀毒软件的系统安全防护 在数字时代,电脑病毒就像潜伏的"网络幽灵",从窃取隐私的木马到消耗资源的蠕虫,时刻威胁着系统安全。当杀毒软件失效或遭遇新型威胁时,手动排查病毒便…