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 核心逻辑图
下面这张图展示了三种工厂模式的关系和演进路径:
4.6 避坑指南
我曾经踩过的坑:
- 静态变量的线程安全问题:简单工厂里用static变量返回,在多任务环境下会出问题。后来我改成动态分配或者传入缓冲区。
- 函数指针表不要放在栈上:有次我把工厂的函数指针表定义在局部变量里,结果返回后指针悬空,调试了一整天。
- 抽象工厂的接口粒度:别把不相关的产品硬凑到一个工厂里。比如把传感器和显示器放一起,看起来都是硬件,但业务上它们没有“必须配套”的关系。
我的习惯:在嵌入式C语言中,我通常用简单工厂 + 注册机制。每个模块在初始化时把自己注册到工厂里,这样既保持了简单工厂的易用性,又有了工厂方法的扩展性。说白了,就是取个中间值。
工厂模式的核心,就是把“创建对象”这个容易变化的部分封装起来。在C语言里,虽然没有多态,但函数指针给了我们足够的灵活性。记住一点:模式是工具,不是目的。别为了用模式而用模式。