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,在硬件抽象层。用对了地方,它就是神器;用错了,就是累赘。