4. 抽象工厂模式:创建相关或依赖对象的家族
抽象工厂模式,这个名字听起来有点唬人。说白了,它就是用来解决「一堆东西要一起造」的问题。
我刚开始做嵌入式那会儿,总觉得工厂模式就是 new 一个对象。后来被现实狠狠教育了一回——当你需要切换整个传感器系列、换掉一整套 BSP 驱动时,才会明白抽象工厂有多香。
4.1 为什么需要抽象工厂?
你想想看,一个嵌入式系统里,传感器往往不是单独存在的。温度传感器、湿度传感器、气压传感器,它们经常组成一个「家族」。今天用 A 公司的方案,明天可能换成 B 公司的。
如果每个传感器都单独 new,代码里到处都是 if-else。换方案时,改到你怀疑人生。
抽象工厂的核心思想:提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。
关键点:抽象工厂强调的是「产品族」的概念。不是单个产品,而是一整套产品。
4.2 在 BSP 中的应用场景
BSP(板级支持包)是抽象工厂的绝佳舞台。我做过一个项目,需要支持三块不同的开发板,每块板子的外设驱动完全不同。
典型的 BSP 抽象工厂场景:
- 传感器系列:温度 + 湿度 + 气压,来自同一厂商
- 通信接口:UART + SPI + I2C,不同板子实现不同
- 存储设备:Flash + EEPROM + SD 卡,同一平台配套使用
- 显示方案:LCD + 触摸 + 背光,整套替换
4.3 代码示例:传感器家族工厂
直接看代码吧。这是我之前一个环境监测项目里的真实结构,我简化了一下:
// 抽象产品:传感器接口
typedef struct {
void (*init)(void);
float (*read)(void);
} Sensor;
// 具体产品:Bosch 温度传感器
static void bosch_temp_init(void) {
// 初始化 Bosch 温度传感器
printf("Bosch 温度传感器初始化\n");
}
static float bosch_temp_read(void) {
return 25.3f; // 模拟读取
}
Sensor bosch_temperature = {
.init = bosch_temp_init,
.read = bosch_temp_read
};
// 具体产品:Bosch 湿度传感器
static void bosch_humidity_init(void) {
printf("Bosch 湿度传感器初始化\n");
}
static float bosch_humidity_read(void) {
return 60.5f;
}
Sensor bosch_humidity = {
.init = bosch_humidity_init,
.read = bosch_humidity_read
};
// 抽象工厂
typedef struct {
Sensor* (*create_temperature)(void);
Sensor* (*create_humidity)(void);
Sensor* (*create_pressure)(void);
} SensorFactory;
// 具体工厂:Bosch 系列
static Sensor* bosch_create_temp(void) {
return &bosch_temperature;
}
static Sensor* bosch_create_humidity(void) {
return &bosch_humidity;
}
SensorFactory bosch_factory = {
.create_temperature = bosch_create_temp,
.create_humidity = bosch_create_humidity,
.create_pressure = bosch_create_pressure // 省略实现
};
我的习惯:把工厂定义成全局常量,放在单独的 factory_config.c 里。换方案时,只需要改这一个文件。
4.4 抽象工厂 vs 工厂方法
很多初学者会搞混这两个模式。我简单说说区别:
| 对比项 | 工厂方法 | 抽象工厂 |
|---|---|---|
| 关注点 | 单个产品 | 产品家族 |
| 扩展方式 | 增加新的工厂子类 | 增加新的工厂实现 |
| 典型场景 | 一种传感器多种型号 | 整套传感器方案切换 |
| 代码复杂度 | 较低 | 较高,但更灵活 |
说白了,如果你只需要换一个传感器,用工厂方法就够了。但如果你要整套换——温度、湿度、气压、加速度计全部换掉——抽象工厂才是正解。
4.5 避坑指南
我曾经踩过的坑:
- 过度设计:项目只有一种传感器方案,硬上抽象工厂。代码翻了一倍,维护成本反而高了。
- 接口不一致:不同厂商的传感器,返回值的单位不同。抽象工厂不负责这个,你得在接口层面统一。
- 初始化顺序:有些传感器依赖其他传感器先初始化。抽象工厂只管创建,不管依赖关系。
嗯,这里要注意:抽象工厂不是银弹。它解决的是「产品族切换」的问题,不是「对象创建」的全部问题。
4.6 实际项目中的取舍
我个人的经验是:在 BSP 层,抽象工厂最适合用在「平台适配层」。比如你的产品要跑在 STM32、GD32、AT32 三款芯片上,每款芯片的 GPIO、定时器、DMA 实现都不同。
这时候抽象工厂就派上用场了:
// 平台抽象工厂
typedef struct {
GpioInterface* (*create_gpio)(void);
TimerInterface* (*create_timer)(void);
DmaInterface* (*create_dma)(void);
} PlatformFactory;
// STM32 平台
PlatformFactory stm32_factory = { ... };
// GD32 平台
PlatformFactory gd32_factory = { ... };
这样做的好处是:应用层代码完全不用关心底层是什么芯片。换平台时,只需要换一个工厂指针。
总结一下:抽象工厂模式在嵌入式系统里,最适合管理「成套的硬件资源」。它让代码的移植性大大提升,代价是增加了一层抽象。要不要用,取决于你的产品需要支持多少种方案。
我记得有一次评审代码,看到有人把抽象工厂用在了业务逻辑层。嗯,那其实没必要。抽象工厂的舞台在底层,在 BSP,在硬件抽象层。用对了地方,它就是神器;用错了,就是累赘。