4. 工厂模式(Factory):简单工厂、工厂方法、抽象工厂在C语言中的模拟实现

工厂模式,说白了就是帮你“生产对象”的套路。

我早年做嵌入式开发时,经常遇到这样的场景:今天用A型号的传感器,明天客户说要换B型号。如果代码里到处是 if (type == A) ... else if (type == B) ...,改起来真是要命。工厂模式就是来解决这个问题的。

在C语言里,我们没有class和new关键字,但用结构体加函数指针,照样能玩出花来。咱们一个一个看。

4.1 简单工厂模式

简单工厂,其实就是一个函数。你告诉它“我要什么”,它给你造出来。

我记得有一次做数据采集项目,需要支持UART、SPI、I2C三种通信接口。每个接口的初始化流程不一样,但上层调用逻辑是一样的。我当时就用简单工厂搞定的。

核心思想:用一个函数封装对象的创建逻辑,调用者不需要知道具体怎么创建。

// 定义通信接口的结构体
typedef struct {
    void (*init)(void);
    void (*send)(uint8_t *data, uint32_t len);
    void (*recv)(uint8_t *data, uint32_t len);
} CommInterface;

// UART实现
static void uart_init(void) { /* UART初始化代码 */ }
static void uart_send(uint8_t *data, uint32_t len) { /* UART发送 */ }
static void uart_recv(uint8_t *data, uint32_t len) { /* UART接收 */ }

// SPI实现
static void spi_init(void) { /* SPI初始化代码 */ }
static void spi_send(uint8_t *data, uint32_t len) { /* SPI发送 */ }
static void spi_recv(uint8_t *data, uint32_t len) { /* SPI接收 */ }

// 简单工厂函数
CommInterface* create_comm(CommType type) {
    static CommInterface interface;
    switch(type) {
        case COMM_UART:
            interface.init = uart_init;
            interface.send = uart_send;
            interface.recv = uart_recv;
            break;
        case COMM_SPI:
            interface.init = spi_init;
            interface.send = spi_send;
            interface.recv = spi_recv;
            break;
        // ... 其他类型
    }
    return &interface;
}

我的经验:简单工厂适合产品种类不多、变化不频繁的场景。如果产品类型超过5种,建议考虑工厂方法模式,否则switch-case会变得臃肿。

4.2 工厂方法模式

工厂方法比简单工厂更进一步。它把“创建对象”这个动作也抽象成接口,每个具体产品对应一个具体工厂。

你想想看,如果有一天要新增一种蓝牙通信,简单工厂就得改那个switch-case。而工厂方法模式,你只需要新增一个蓝牙工厂,完全不用动已有代码。

// 抽象工厂接口
typedef struct {
    CommInterface* (*create)(void);
} Factory;

// UART工厂
static CommInterface* create_uart(void) {
    static CommInterface uart = {
        .init = uart_init,
        .send = uart_send,
        .recv = uart_recv
    };
    return &uart;
}
static Factory uart_factory = { .create = create_uart };

// SPI工厂
static CommInterface* create_spi(void) {
    static CommInterface spi = {
        .init = spi_init,
        .send = spi_send,
        .recv = spi_recv
    };
    return &spi;
}
static Factory spi_factory = { .create = create_spi };

// 使用时
Factory *factory = get_factory(COMM_UART);
CommInterface *comm = factory->create();

注意:工厂方法模式会增加代码量。每个产品都要配一个工厂结构体。我一般只在产品类型可能频繁扩展,或者产品初始化逻辑差异很大时才用。

4.3 抽象工厂模式

抽象工厂,是工厂的工厂。它用来创建一组相关的产品。

举个例子:你做一个智能家居系统,有小米生态和华为生态。每个生态里都有传感器、开关、摄像头。你不能把小米的传感器和华为的开关混着用吧?抽象工厂就是保证“同一套产品族”的一致性。

// 产品族:传感器、开关、摄像头
typedef struct {
    void (*read_data)(void);
} Sensor;

typedef struct {
    void (*toggle)(void);
} Switch;

typedef struct {
    void (*start_record)(void);
} Camera;

// 抽象工厂
typedef struct {
    Sensor* (*create_sensor)(void);
    Switch* (*create_switch)(void);
    Camera* (*create_camera)(void);
} EcosystemFactory;

// 小米生态工厂
static Sensor* xiaomi_create_sensor(void) { /* 小米传感器 */ }
static Switch* xiaomi_create_switch(void) { /* 小米开关 */ }
static Camera* xiaomi_create_camera(void) { /* 小米摄像头 */ }
EcosystemFactory xiaomi_factory = {
    .create_sensor = xiaomi_create_sensor,
    .create_switch = xiaomi_create_switch,
    .create_camera = xiaomi_create_camera
};

// 华为生态工厂类似,略

什么时候用抽象工厂?当你的系统需要支持多个产品族,且每个产品族内的产品必须配套使用时。我曾经在一个车载项目中用过,支持博世和大陆两个供应商的传感器套件,抽象工厂让切换供应商变得非常干净。

4.4 三种工厂模式的对比

模式 复杂度 扩展性 适用场景
简单工厂 低 低(改代码) 产品少、变化少
工厂方法 中 高(加类) 产品多、需扩展
抽象工厂 高 高(加产品族) 多产品族、需配套

4.5 核心逻辑图

下面这张图展示了三种工厂模式的关系和演进路径:

工厂模式演进关系图 简单工厂 一个函数创建所有产品 switch-case 分发 扩展困难 工厂方法 每个产品一个工厂 符合开闭原则 需要产品族 抽象工厂 创建一组相关产品 保证产品族一致性 我的建议: • 项目初期或产品少于3种:用简单工厂,快速迭代 • 产品种类多且可能扩展:用工厂方法,预留扩展点 • 涉及多个产品族且需配套:用抽象工厂,保证一致性 • 别过度设计!我曾经见过有人为了2个产品硬上抽象工厂,代码量翻了三倍

4.6 避坑指南

我曾经踩过的坑:

  • 静态变量的线程安全问题:简单工厂里用static变量返回,在多任务环境下会出问题。后来我改成动态分配或者传入缓冲区。
  • 函数指针表不要放在栈上:有次我把工厂的函数指针表定义在局部变量里,结果返回后指针悬空,调试了一整天。
  • 抽象工厂的接口粒度:别把不相关的产品硬凑到一个工厂里。比如把传感器和显示器放一起,看起来都是硬件,但业务上它们没有“必须配套”的关系。

我的习惯:在嵌入式C语言中,我通常用简单工厂 + 注册机制。每个模块在初始化时把自己注册到工厂里,这样既保持了简单工厂的易用性,又有了工厂方法的扩展性。说白了,就是取个中间值。

工厂模式的核心,就是把“创建对象”这个容易变化的部分封装起来。在C语言里,虽然没有多态,但函数指针给了我们足够的灵活性。记住一点:模式是工具,不是目的。别为了用模式而用模式。

可可精品资料库