作为C语言开发者,我们每天都在和各种"对象"打交道------传感器、外设、缓冲区、任务控制块......尤其是做嵌入式开发时,经常要写一堆类似的初始化代码:温度传感器要初始化I2C接口,光照传感器要配置SPI时序,湿度传感器又要设置GPIO中断。这些代码散落各处时,不仅冗余难维护,后续加新传感器还得在整个项目里"翻箱倒柜"改代码。
有没有一种方式,能把这些对象的创建逻辑统一管理起来?调用者不用关心具体怎么初始化,只要告诉系统"我要一个温度传感器",就能直接拿到可用的对象?这就是今天要聊的------创建型模式中的简单工厂模式。用C语言的函数指针数组+创建函数封装,就能轻松实现,尤其适合嵌入式设备的外设管理场景。
一、基础铺垫:什么是简单工厂模式?
先抛开复杂的设计模式术语,用大白话解释:简单工厂模式就像一个"对象加工厂"。这个工厂里封装了所有对象的创建逻辑,调用者不需要知道对象是怎么被初始化、配置的,只需要向工厂传递一个"类型参数"(比如"温度传感器""光照传感器"),工厂就会返回一个符合要求的、可以直接使用的对象。
从结构上看,简单工厂模式主要包含三个核心部分:
-
产品(Product):所有需要创建的对象的抽象统一接口。比如所有传感器都需要有"读取数据""启动采集"的功能,我们就可以定义一个传感器抽象结构体,包含这些功能的函数指针。
-
具体产品(Concrete Product):实现了抽象产品接口的具体对象。比如温度传感器、光照传感器,各自实现"读取数据"的具体逻辑(读I2C寄存器、读SPI缓冲区等)。
-
工厂(Factory):核心部分,封装了创建具体产品的逻辑。提供一个统一的创建接口,根据输入的类型参数,调用对应的具体产品创建函数,返回抽象产品指针(多态特性)。
对C语言来说,没有类和继承,但我们可以用"结构体+函数指针"模拟抽象接口,用函数指针数组管理不同产品的创建函数------这就是C语言实现简单工厂模式的核心思路。
二、核心模式实现:C语言如何落地?
接下来我们一步步实现简单工厂模式,核心是"抽象产品定义→具体产品实现→工厂封装创建逻辑"三步走,重点关注函数指针数组的使用。
2.1 第一步:定义抽象产品接口
首先定义所有传感器的统一抽象接口,这里用结构体包含功能函数指针,所有具体传感器都要实现这些函数:
c
#include <stdio.h>
#include <stdlib.h>
// 传感器类型枚举(工厂的输入参数)
typedef enum {
SENSOR_TEMPERATURE, // 温度传感器
SENSOR_LIGHT, // 光照传感器
SENSOR_HUMIDITY // 湿度传感器
} SensorType;
// 抽象产品:传感器接口
typedef struct {
// 读取数据函数(不同传感器实现不同)
float (*read_data)(void);
// 启动采集函数
void (*start_collect)(void);
// 私有数据(具体传感器的配置信息,比如I2C地址、SPI引脚等)
void *private_data;
} Sensor;
这里的Sensor结构体就是抽象产品,read_data和start_collect是所有传感器的通用功能,private_data用来存储具体传感器的私有配置(比如温度传感器的I2C地址0x48,光照传感器的SPI片选引脚PA4),实现数据封装。
2.2 第二步:实现具体产品
针对每种传感器,实现抽象接口的函数,并定义私有配置结构体。以温度传感器(I2C接口)和光照传感器(SPI接口)为例:
c
// 具体产品1:温度传感器私有配置
typedef struct {
uint8_t i2c_addr; // I2C地址
uint8_t reg_addr; // 数据寄存器地址
} TempSensorPriv;
// 温度传感器读取数据(实现抽象接口)
static float temp_sensor_read_data(void) {
TempSensorPriv *priv = (TempSensorPriv *)((Sensor *)this)->private_data;
// 模拟I2C读取逻辑:发送地址→读取寄存器→解析数据
printf("读取I2C地址0x%x的温度寄存器0x%x\n", priv->i2c_addr, priv->reg_addr);
return 25.5f; // 模拟数据
}
// 温度传感器启动采集
static void temp_sensor_start_collect(void) {
printf("温度传感器启动采集,采样率10Hz\n");
}
// 具体产品2:光照传感器私有配置
typedef struct {
uint8_t spi_cs_pin; // SPI片选引脚
uint16_t max_range; // 最大测量范围
} LightSensorPriv;
// 光照传感器读取数据(实现抽象接口)
static float light_sensor_read_data(void) {
LightSensorPriv *priv = (LightSensorPriv *)((Sensor *)this)->private_data;
// 模拟SPI读取逻辑:拉低片选→发送命令→读取数据→拉高片选
printf("拉低SPI片选引脚PA%d,读取光照数据\n", priv->spi_cs_pin);
return 800.0f; // 模拟数据
}
// 光照传感器启动采集
static void light_sensor_start_collect(void) {
printf("光照传感器启动采集,最大范围%d lux\n", ((LightSensorPriv *)((Sensor *)this)->private_data)->max_range);
}
这里的关键是:每个具体传感器都实现了抽象接口的read_data和start_collect函数,并且通过private_data访问自己的私有配置,实现了"接口统一,实现各异"的多态效果。
2.3 第三步:工厂封装创建逻辑
工厂的核心作用是"根据类型创建对象",我们用函数指针数组 存储不同传感器的创建函数,然后提供一个统一的create_sensor接口,根据输入的SensorType调用对应的创建函数:
c
// 前向声明:具体传感器的创建函数
static Sensor *create_temp_sensor(void);
static Sensor *create_light_sensor(void);
// 函数指针类型:创建传感器的函数指针
typedef Sensor *(*CreateSensorFunc)(void);
// 工厂的核心:创建函数指针数组(映射类型→创建函数)
static CreateSensorFunc create_func_array[] = {
[SENSOR_TEMPERATURE] = create_temp_sensor,
[SENSOR_LIGHT] = create_light_sensor,
// 新增传感器时,只需在这里添加映射
};
// 具体创建温度传感器
static Sensor *create_temp_sensor(void) {
// 1. 分配传感器对象内存
Sensor *sensor = (Sensor *)malloc(sizeof(Sensor));
if (sensor == NULL) return NULL;
// 2. 分配私有配置内存并初始化
TempSensorPriv *priv = (TempSensorPriv *)malloc(sizeof(TempSensorPriv));
if (priv == NULL) {
free(sensor);
return NULL;
}
priv->i2c_addr = 0x48; // 实际项目中可从配置文件读取
priv->reg_addr = 0x00;
// 3. 绑定接口函数(多态核心)
sensor->read_data = temp_sensor_read_data;
sensor->start_collect = temp_sensor_start_collect;
sensor->private_data = priv;
return sensor;
}
// 具体创建光照传感器
static Sensor *create_light_sensor(void) {
Sensor *sensor = (Sensor *)malloc(sizeof(Sensor));
if (sensor == NULL) return NULL;
LightSensorPriv *priv = (LightSensorPriv *)malloc(sizeof(LightSensorPriv));
if (priv == NULL) {
free(sensor);
return NULL;
}
priv->spi_cs_pin = 4; // PA4
priv->max_range = 4096;
sensor->read_data = light_sensor_read_data;
sensor->start_collect = light_sensor_start_collect;
sensor->private_data = priv;
return sensor;
}
// 统一工厂接口:对外提供的创建函数
Sensor *create_sensor(SensorType type) {
// 检查类型合法性
if (type < SENSOR_TEMPERATURE || type > SENSOR_HUMIDITY) {
printf("不支持的传感器类型\n");
return NULL;
}
// 调用对应的创建函数(函数指针数组的核心作用)
CreateSensorFunc create_func = create_func_array[type];
if (create_func == NULL) {
printf("未实现该传感器的创建函数\n");
return NULL;
}
return create_func();
}
// 辅助函数:销毁传感器(避免内存泄漏)
void destroy_sensor(Sensor *sensor) {
if (sensor != NULL) {
if (sensor->private_data != NULL) {
free(sensor->private_data);
}
free(sensor);
}
}
这里的核心是create_func_array函数指针数组:它把"传感器类型"和"具体创建函数"做了映射。当调用create_sensor(SENSOR_TEMPERATURE)时,工厂会自动找到create_temp_sensor函数,创建并返回温度传感器对象。
对调用者来说,完全不用关心I2C、SPI的配置细节,只要调用create_sensor就能拿到可用的传感器,实现了"创建逻辑封装"的核心目标。
三、实战案例:嵌入式传感器管理系统
下面我们用一个完整的嵌入式场景示例,展示简单工厂模式的实际应用:假设我们要开发一个智能硬件的传感器采集系统,需要管理温度、光照两种传感器,后续可能还要添加湿度传感器。
3.1 调用者代码(应用层)
c
int main(void) {
// 1. 从工厂获取温度传感器
Sensor *temp_sensor = create_sensor(SENSOR_TEMPERATURE);
if (temp_sensor != NULL) {
temp_sensor->start_collect(); // 启动采集
printf("温度:%.1f℃\n", temp_sensor->read_data()); // 读取数据
}
// 2. 从工厂获取光照传感器
Sensor *light_sensor = create_sensor(SENSOR_LIGHT);
if (light_sensor != NULL) {
light_sensor->start_collect();
printf("光照:%.1f lux\n", light_sensor->read_data());
}
// 3. 销毁资源(嵌入式中注意内存管理)
destroy_sensor(temp_sensor);
destroy_sensor(light_sensor);
return 0;
}
3.2 运行结果
text
温度传感器启动采集,采样率10Hz
读取I2C地址0x48的温度寄存器0x00
温度:25.5℃
拉低SPI片选引脚PA4,读取光照数据
光照传感器启动采集,最大范围4096 lux
光照:800.0 lux
3.3 新增传感器的成本
如果后续需要添加湿度传感器(UART接口),只需做3件事,完全不用修改应用层代码:
-
在
SensorType枚举中添加SENSOR_HUMIDITY; -
实现湿度传感器的私有配置、
read_data、start_collect函数,以及对应的创建函数create_humidity_sensor; -
在
create_func_array中添加[SENSOR_HUMIDITY] = create_humidity_sensor。
这种"对扩展开放,对修改关闭"的特性,正是简单工厂模式的核心价值,尤其适合嵌入式项目中频繁添加外设的场景。
四、进阶拓展:优缺点与适用场景
任何设计模式都不是银弹,简单工厂模式也有其适用边界,我们结合C语言和嵌入式场景做详细分析。
4.1 优点
-
封装创建逻辑,降低耦合:调用者无需关心对象的初始化细节(I2C/SPI配置、寄存器地址等),只需关注对象的使用,符合"单一职责原则";
-
简化代码,提高可维护性:避免了大量重复的初始化代码,所有创建逻辑集中在工厂中,后续修改配置只需改工厂或具体产品的代码;
-
扩展方便:新增产品时只需添加具体实现和工厂映射,无需修改现有应用层代码,适配嵌入式项目的迭代需求;
-
C语言友好:基于函数指针数组实现,不依赖复杂语法,完全贴合C语言的底层开发习惯。
4.2 缺点
-
工厂职责过重:所有产品的创建逻辑都集中在一个工厂中,当产品数量过多时,工厂会变得庞大,难以维护(这也是它被称为"简单"工厂的原因,复杂场景可考虑工厂方法模式);
-
违背"开闭原则"的极致要求 :虽然新增产品不用改应用层,但需要修改工厂的
create_func_array和SensorType枚举,属于"局部修改"; -
内存管理风险 :C语言需要手动管理
Sensor和private_data的内存,若忘记调用destroy_sensor,会导致内存泄漏(嵌入式中可结合内存池优化)。
4.3 适用场景
-
产品类型较少且固定:比如嵌入式设备中管理3-5种外设(传感器、通信模块等),工厂不会过于庞大;
-
创建逻辑相对简单:若对象的初始化步骤极其复杂(比如需要跨模块配置、依赖多个外部资源),可考虑更复杂的工厂方法模式;
-
需要统一管理对象生命周期 :比如嵌入式中需要统一初始化、销毁外设,工厂可配合
destroy_sensor函数实现生命周期管理; -
希望降低调用者的使用成本:比如团队协作中,让应用层开发者无需了解底层外设细节,直接通过工厂获取可用对象。
4.4 嵌入式场景的优化建议
-
避免动态内存分配 :嵌入式中动态内存(
malloc)可能导致内存碎片,可将Sensor和私有配置结构体定义为全局变量或静态变量,工厂直接返回其指针; -
添加错误处理机制:在创建函数中增加对硬件初始化结果的检查(比如I2C初始化失败时返回NULL),并在应用层做容错处理;
-
结合配置文件:将传感器的私有配置(I2C地址、SPI引脚等)存储在配置文件或Flash中,工厂创建对象时从配置中读取参数,提高灵活性。
五、总结
简单工厂模式的核心思想是"统一工厂封装对象创建逻辑",在C语言中通过"抽象结构体+函数指针+函数指针数组"的组合,就能完美落地。它特别适合嵌入式设备中的外设管理场景,能有效降低代码耦合、提高可维护性。
记住:设计模式的本质是"解决特定场景下的代码问题",不是炫技的工具。在嵌入式开发中,只要能让代码更简洁、更易维护、更适配迭代需求,就是好的设计。
如果这篇文章对你有帮助,欢迎点赞、收藏,关注我后续更新更多C语言+嵌入式相关的设计模式实战内容!如果有疑问或不同的实现思路,也欢迎在评论区留言讨论~