news 2026/6/10 19:16:49

第 4 篇:策略模式 (Strategy) —— 算法的热插拔艺术

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
第 4 篇:策略模式 (Strategy) —— 算法的热插拔艺术

专栏导读:你是否遇到过这种崩溃瞬间:产品卖给 A 客户要用 Modbus 协议,卖给 B 客户要用私有协议,卖给 C 客户要加密传输。你的代码里是不是充斥着无数的#ifdef CUSTOMER_A或者if (mode == 1)? 策略模式教你用 C 语言实现“多态”,让算法像 USB 设备一样,插上就能用,拔掉就消失。


1. 场景还原 (The Pain)

假设你正在维护一款智能门锁。核心功能是“验证密码”。 起初,只需要简单的明文比对。后来,高配版加入了指纹(Hash 校验),海外版要求符合安全规范(CRC32 或 AES)。

菜鸟的写法:条件判断的地狱

typedef enum { AUTH_SIMPLE, AUTH_CRC32, AUTH_HASH } AuthMode;

bool Verify_Password(uint8_t* input, uint8_t* stored, int len, AuthMode mode) {
if (mode == AUTH_SIMPLE) {
// 明文比对
return (memcmp(input, stored, len) == 0);
}
else if (mode == AUTH_CRC32) {
// 还要去包含 CRC 的头文件,耦合严重
uint32_t crc_in = Cal_CRC32(input, len);
uint32_t crc_store = *(uint32_t*)stored;
return (crc_in == crc_store);
}
else if (mode == AUTH_HASH) {
// ... 又是一大坨代码 ...
}
return false;
}

架构师的审视

这种代码违反了设计模式的最高天条:开闭原则 (Open-Closed Principle)—— 对扩展开放,对修改关闭。

  1. 耦合过重Verify_Password函数必须认识所有的算法。如果你要加一个新的“视网膜扫描”,你得修改这个经过测试的稳定函数,万一改坏了,旧功能也挂了。

  2. 代码臃肿:低配版芯片 Flash 只有 32KB,根本装不下 AES 库,但因为写在一个文件里,链接器很难剥离未用到的代码(除非编译器优化极强)。


2. 模式图解 (The Concept)

策略模式的核心是将**“做什么” (Interface)** 和“怎么做” (Implementation)分离。

  • Context (环境):持有算法的指针,不关心具体是哪个算法。

  • Strategy (接口):定义一套标准的函数指针结构体(V-Table)。

  • Concrete Strategy (具体策略):实现具体的算法。


3. 代码实战 (The Code)

C 语言实现多态的秘密武器:结构体 + 函数指针

3.1 定义接口 (The Interface)

这是所有算法必须遵守的契约。

// IValidator.h
#include <stdint.h>
#include <stdbool.h>

// 前置声明,类似 C++ 的 this 指针
typedef struct Validator_t Validator;

// 策略接口定义 (虚函数表)
typedef struct {
// 初始化算法
void (*init)(Validator* self);
// 执行计算
uint32_t (*calculate)(Validator* self, const uint8_t* data, uint32_t len);
// 销毁/清理 (可选)
void (*deinit)(Validator* self);
} ValidatorOps;

// 基类结构体
struct Validator_t {
const ValidatorOps* ops; // 指向虚表的指针
// 可以添加公共属性,如超时时间等
uint32_t timeout;
};

3.2 具体策略实现 (Concrete Strategies)

我们把每个算法写在独立的.c文件里,互不干扰。

策略 A:简单校验 (Simple)

// Validator_Simple.c
#include "IValidator.h"

static uint32_t simple_cal(Validator* self, const uint8_t* data, uint32_t len) {
uint32_t sum = 0;
for(int i=0; i<len; i++) sum += data[i];
return sum; // 简单的累加和
}

// 定义具体的虚表
static const ValidatorOps s_simple_ops = {
.init = NULL, // 不需要初始化
.calculate = simple_cal,
};

// 构造函数
void Validator_Simple_Init(Validator* v) {
v->ops = &s_simple_ops; // 核心:绑定指针
}

策略 B:CRC32 校验 (CRC32)

// Validator_CRC32.c
#include "IValidator.h"
#include "stm32_crc.h" // 依赖硬件库

static void crc_init(Validator* self) {
__HAL_RCC_CRC_CLK_ENABLE(); // 开启硬件时钟
}

static uint32_t crc_cal(Validator* self, const uint8_t* data, uint32_t len) {
return HAL_CRC_Calculate(&hcrc, (uint32_t*)data, len);
}

static const ValidatorOps s_crc32_ops = {
.init = crc_init,
.calculate = crc_cal,
};

void Validator_CRC32_Init(Validator* v) {
v->ops = &s_crc32_ops;
}

3.3 业务调用 (Context)

// main.c
#include "IValidator.h"

// 全局的校验器实例
Validator g_current_validator;

// 根据配置选择策略 (Factory 模式的雏形)
void System_Init() {
uint8_t config = Read_Flash_Config();

if (config == MODE_HIGH_END) {
Validator_CRC32_Init(&g_current_validator);
} else {
Validator_Simple_Init(&g_current_validator);
}

// 初始化选定的算法
if (g_current_validator.ops->init) {
g_current_validator.ops->init(&g_current_validator);
}
}

// 业务逻辑:完全不需要知道底层是 CRC 还是 Sum
bool Verify_Packet(uint8_t* data, int len, uint32_t expected) {
// 多态调用!
uint32_t result = g_current_validator.ops->calculate(&g_current_validator, data, len);

return (result == expected);
}

4. 内存与性能分析 (The Cost)

空间开销

  • Flash: 这种写法对 Linker 非常友好。如果你在System_Init里没有调用Validator_CRC32_Init,聪明的链接器(开启--gc-sections)会自动把Validator_CRC32.c的代码完全剔除。这就实现了**“用多少,链多少”**。

  • RAM: 每个策略实例只需要一个指针大小(4 字节)。虚表ValidatorOps声明为const,存放在 Flash 中,不占 RAM。

时间开销

  • 间接调用:ops->calculate(...)是一次间接函数调用 (Indirect Call)

  • 汇编视角:

    • 直接调用:BL 0x08001234(跳转到固定地址)

    • 间接调用:LDR R0, [R1]; BLX R0(先读地址再跳转)

  • 性能损耗: 相比直接调用,多了 2-3 个指令周期。

  • 架构师建议:不要在百万次的极速循环内部使用策略模式(例如像素级渲染)。但对于通信包校验、按键处理、传感器读取这种毫秒级任务,性能损耗可以忽略不计,换来的是巨大的架构灵活性。


5. 变种与延伸 (The Evolution)

5.1 运行时动态切换 (Hot Swap)

这就是标题中“热插拔”的由来。 比如一个无线接收模块,信号好时用Strategy_Fast(无校验,速度快),信号差时自动切换为Strategy_Reliable(高强度校验)。 你只需要执行v->ops = &s_reliable_ops;,业务层代码无需任何变动。

5.2 依赖注入与打桩 (Mocking for Test)

这是策略模式在单元测试中的杀手级应用。 在 PC 上跑单元测试时,无法调用HAL_CRC_Calculate(因为没有硬件)。 你可以写一个Validator_Mock.c,在里面伪造数据。

// Test_Main.c
void Test_Logic() {
Validator v;
Validator_Mock_Init(&v); // 注入假对象

// 测试业务逻辑,此时 calculate 调用的是假的桩函数
Verify_Packet(..., ...);
}

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/10 16:13:52

商城活动说明

一、活动说明抽奖活动凭借着以小博大的杠杆效应、低门槛参与、高奖励诱惑的活动机制&#xff0c;无论是线下门店促销&#xff0c;还是线上活动&#xff0c;都被广泛用于拉新、促活、获客等增长环节。二、功能说明系统提供的抽奖环节有&#xff1a;积分抽奖&#xff0c;下单支付…

作者头像 李华
网站建设 2026/6/9 23:43:30

25.4 进度类

&#x1f31f; 一、单代号网络图&#xff08;PDM / 前导图法&#xff09;✅ 定义前导图法&#xff08;Precedence Diagramming Method, PDM&#xff09;&#xff1a;用矩形/方框&#xff08;节点&#xff09;表示活动&#xff0c;箭头表示逻辑关系。节点需编号&#xff0c;箭线…

作者头像 李华
网站建设 2026/5/23 15:21:56

收藏!2025秋招真相:IT仍是王者,AI算法岗年薪40万领跑全场

秋招战场的冰火两重天&#xff0c;今年格外刺眼。一边是无数毕业生为“月薪过万”辗转焦虑&#xff0c;投出的简历石沉大海&#xff1b;另一边&#xff0c;不少瞄准热门赛道的毕业生&#xff0c;早已将“年薪40万”纳入囊中之物&#xff0c;成为秋招里的“天选赢家”。 近日&a…

作者头像 李华
网站建设 2026/6/10 16:02:46

Therma Wave 14-004693

产品概述Therma Wave 14-004693 Rev D是用于半导体制造或检测设备的电气总成组件&#xff0c;通常与晶圆处理或真空系统相关。该型号可能涉及温度控制、真空环境维持或信号传输功能&#xff0c;具体应用取决于设备型号。功能特点晶圆处理&#xff1a;可能包含晶圆定位、温度监控…

作者头像 李华
网站建设 2026/6/10 16:02:17

[运营进阶] 店铺图片风格杂乱?浅析如何利用 AI 批量统一视觉规范,打造“大牌感”Listing

品牌出海 视觉营销 跨境电商 AI工具 自动化办公 图片处理前言在跨境电商从“野蛮生长”转向“品牌出海”的今天&#xff0c;Listing 的视觉质量 往往决定了买家对品牌的首因效应。对于拥有独立供应链的大卖来说&#xff0c;他们可以花费巨资统一拍摄、统一修图。但对于大多数中…

作者头像 李华
网站建设 2026/5/31 12:14:19

基于级联前向BP神经网络(CFBP)的数据回归预测及Matlab实现

基于级联前向BP神经网络(CFBP)的数据回归预测 CFBP回归 matlab代码 注&#xff1a;暂无Matlab版本要求 -- 推荐 2018B 版本及以上 在数据预测领域&#xff0c;神经网络一直是备受瞩目的工具。今天咱们来聊聊基于级联前向BP神经网络&#xff08;CFBP&#xff09;的数据回归预测&…

作者头像 李华