创建型模式:简单工厂模式(C语言实现)

作为C语言开发者,我们每天都在和各种"对象"打交道------传感器、外设、缓冲区、任务控制块......尤其是做嵌入式开发时,经常要写一堆类似的初始化代码:温度传感器要初始化I2C接口,光照传感器要配置SPI时序,湿度传感器又要设置GPIO中断。这些代码散落各处时,不仅冗余难维护,后续加新传感器还得在整个项目里"翻箱倒柜"改代码。

有没有一种方式,能把这些对象的创建逻辑统一管理起来?调用者不用关心具体怎么初始化,只要告诉系统"我要一个温度传感器",就能直接拿到可用的对象?这就是今天要聊的------创建型模式中的简单工厂模式。用C语言的函数指针数组+创建函数封装,就能轻松实现,尤其适合嵌入式设备的外设管理场景。

一、基础铺垫:什么是简单工厂模式?

先抛开复杂的设计模式术语,用大白话解释:简单工厂模式就像一个"对象加工厂"。这个工厂里封装了所有对象的创建逻辑,调用者不需要知道对象是怎么被初始化、配置的,只需要向工厂传递一个"类型参数"(比如"温度传感器""光照传感器"),工厂就会返回一个符合要求的、可以直接使用的对象。

从结构上看,简单工厂模式主要包含三个核心部分:

  1. 产品(Product):所有需要创建的对象的抽象统一接口。比如所有传感器都需要有"读取数据""启动采集"的功能,我们就可以定义一个传感器抽象结构体,包含这些功能的函数指针。

  2. 具体产品(Concrete Product):实现了抽象产品接口的具体对象。比如温度传感器、光照传感器,各自实现"读取数据"的具体逻辑(读I2C寄存器、读SPI缓冲区等)。

  3. 工厂(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_datastart_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_datastart_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件事,完全不用修改应用层代码:

  1. SensorType枚举中添加SENSOR_HUMIDITY

  2. 实现湿度传感器的私有配置、read_datastart_collect函数,以及对应的创建函数create_humidity_sensor

  3. create_func_array中添加[SENSOR_HUMIDITY] = create_humidity_sensor

这种"对扩展开放,对修改关闭"的特性,正是简单工厂模式的核心价值,尤其适合嵌入式项目中频繁添加外设的场景。

四、进阶拓展:优缺点与适用场景

任何设计模式都不是银弹,简单工厂模式也有其适用边界,我们结合C语言和嵌入式场景做详细分析。

4.1 优点

  1. 封装创建逻辑,降低耦合:调用者无需关心对象的初始化细节(I2C/SPI配置、寄存器地址等),只需关注对象的使用,符合"单一职责原则";

  2. 简化代码,提高可维护性:避免了大量重复的初始化代码,所有创建逻辑集中在工厂中,后续修改配置只需改工厂或具体产品的代码;

  3. 扩展方便:新增产品时只需添加具体实现和工厂映射,无需修改现有应用层代码,适配嵌入式项目的迭代需求;

  4. C语言友好:基于函数指针数组实现,不依赖复杂语法,完全贴合C语言的底层开发习惯。

4.2 缺点

  1. 工厂职责过重:所有产品的创建逻辑都集中在一个工厂中,当产品数量过多时,工厂会变得庞大,难以维护(这也是它被称为"简单"工厂的原因,复杂场景可考虑工厂方法模式);

  2. 违背"开闭原则"的极致要求 :虽然新增产品不用改应用层,但需要修改工厂的create_func_arraySensorType枚举,属于"局部修改";

  3. 内存管理风险 :C语言需要手动管理Sensorprivate_data的内存,若忘记调用destroy_sensor,会导致内存泄漏(嵌入式中可结合内存池优化)。

4.3 适用场景

  1. 产品类型较少且固定:比如嵌入式设备中管理3-5种外设(传感器、通信模块等),工厂不会过于庞大;

  2. 创建逻辑相对简单:若对象的初始化步骤极其复杂(比如需要跨模块配置、依赖多个外部资源),可考虑更复杂的工厂方法模式;

  3. 需要统一管理对象生命周期 :比如嵌入式中需要统一初始化、销毁外设,工厂可配合destroy_sensor函数实现生命周期管理;

  4. 希望降低调用者的使用成本:比如团队协作中,让应用层开发者无需了解底层外设细节,直接通过工厂获取可用对象。

4.4 嵌入式场景的优化建议

  1. 避免动态内存分配 :嵌入式中动态内存(malloc)可能导致内存碎片,可将Sensor和私有配置结构体定义为全局变量或静态变量,工厂直接返回其指针;

  2. 添加错误处理机制:在创建函数中增加对硬件初始化结果的检查(比如I2C初始化失败时返回NULL),并在应用层做容错处理;

  3. 结合配置文件:将传感器的私有配置(I2C地址、SPI引脚等)存储在配置文件或Flash中,工厂创建对象时从配置中读取参数,提高灵活性。

五、总结

简单工厂模式的核心思想是"统一工厂封装对象创建逻辑",在C语言中通过"抽象结构体+函数指针+函数指针数组"的组合,就能完美落地。它特别适合嵌入式设备中的外设管理场景,能有效降低代码耦合、提高可维护性。

记住:设计模式的本质是"解决特定场景下的代码问题",不是炫技的工具。在嵌入式开发中,只要能让代码更简洁、更易维护、更适配迭代需求,就是好的设计。

如果这篇文章对你有帮助,欢迎点赞、收藏,关注我后续更新更多C语言+嵌入式相关的设计模式实战内容!如果有疑问或不同的实现思路,也欢迎在评论区留言讨论~

相关推荐
范纹杉想快点毕业1 小时前
嵌入式工程师一年制深度进阶学习计划(纯技术深耕版)
linux·运维·服务器·c语言·数据库·算法
2501_944424122 小时前
Flutter for OpenHarmony游戏集合App实战之记忆翻牌表情图案
开发语言·javascript·flutter·游戏·harmonyos
爱吃大芒果2 小时前
Flutter for OpenHarmony前置知识:Dart 语法核心知识点总结(上)
开发语言·flutter·dart
2501_944424122 小时前
Flutter for OpenHarmony游戏集合App实战之数字拼图打乱排列
android·开发语言·flutter·游戏·harmonyos
运维行者_2 小时前
OpManager 对接 ERP 避坑指南,网络自动化提升数据同步效率
运维·服务器·开发语言·网络·microsoft·网络安全·php
爱编程的小庄2 小时前
Rust初识
开发语言·rust
23124_802 小时前
热身签到-ctfshow
开发语言·python
小白学大数据2 小时前
移动端Temu App数据抓包与商品爬取方案
开发语言·爬虫·python
吃吃喝喝小朋友2 小时前
JavaScript文件的操作方法
开发语言·javascript·ecmascript