5、工厂模式:简单工厂、工厂方法、抽象工厂在C中的实现

工厂模式,说白了就是帮你「创建对象」的那层中间人。我刚开始写C的时候,总觉得new一个结构体有啥好封装的?直到项目规模一上来,到处都是malloc和初始化代码,改一个类型得翻遍整个工程……嗯,那时候我才意识到,工厂模式这东西,真不是Java的专利。

今天咱们就聊聊C语言里的三种工厂模式:简单工厂、工厂方法、抽象工厂。我会结合自己踩过的坑,给你讲明白它们各自适合什么场景。

5.1 简单工厂:最直白的封装

简单工厂,其实就是把创建对象的逻辑集中到一个函数里。你传个参数,它返回对应的对象指针。我在做嵌入式驱动开发时,经常用这个模式来创建不同型号的传感器驱动实例。

核心思想:用一个函数代替散落在各处的malloc+初始化代码。

// 简单工厂示例:创建不同形状
typedef struct {
    void (*draw)(void*);
} Shape;

typedef struct {
    Shape base;
    int radius;
} Circle;

typedef struct {
    Shape base;
    int width;
    int height;
} Rect;

// 工厂函数
Shape* create_shape(int type) {
    switch(type) {
        case 0: {
            Circle* c = malloc(sizeof(Circle));
            c->base.draw = circle_draw;
            c->radius = 10;
            return (Shape*)c;
        }
        case 1: {
            Rect* r = malloc(sizeof(Rect));
            r->base.draw = rect_draw;
            r->width = 20;
            r->height = 10;
            return (Shape*)r;
        }
        default:
            return NULL;
    }
}

我的经验:简单工厂适合产品种类少、变化不频繁的场景。我曾经在一个项目里用简单工厂管理5种通信协议,后来协议增加到15种,switch-case变得又臭又长……那时候我就知道,该升级到工厂方法了。

5.2 工厂方法:把选择权交给子类

工厂方法把「创建对象」这个动作抽象成虚函数。每个子类自己决定创建什么对象。说白了,就是把简单工厂里的那个switch-case,拆到不同的子类里去实现。

// 工厂方法模式
typedef struct Factory {
    Shape* (*create)(struct Factory*);
} Factory;

// 圆形工厂
Shape* circle_factory_create(Factory* f) {
    Circle* c = malloc(sizeof(Circle));
    c->base.draw = circle_draw;
    c->radius = 10;
    return (Shape*)c;
}

Factory circle_factory = {
    .create = circle_factory_create
};

// 矩形工厂
Shape* rect_factory_create(Factory* f) {
    Rect* r = malloc(sizeof(Rect));
    r->base.draw = rect_draw;
    r->width = 20;
    r->height = 10;
    return (Shape*)r;
}

Factory rect_factory = {
    .create = rect_factory_create
};

避坑指南:我曾经在工厂方法里忘记释放工厂对象本身,导致内存泄漏。记住:工厂方法只负责创建产品,工厂自己的生命周期要单独管理。

5.3 抽象工厂:创建一组相关对象

抽象工厂解决的是「产品族」的问题。比如你要创建一套UI组件:按钮、文本框、下拉框。在Windows风格下是一套实现,在Mac风格下是另一套。抽象工厂就是用来创建这一整套风格的。

// 抽象工厂:创建UI组件族
typedef struct {
    Button* (*create_button)();
    TextBox* (*create_textbox)();
    DropDown* (*create_dropdown)();
} UIFactory;

// Windows风格工厂
Button* win_create_button() { /* Windows按钮实现 */ }
TextBox* win_create_textbox() { /* Windows文本框实现 */ }
DropDown* win_create_dropdown() { /* Windows下拉框实现 */ }

UIFactory win_factory = {
    .create_button = win_create_button,
    .create_textbox = win_create_textbox,
    .create_dropdown = win_create_dropdown
};

// Mac风格工厂
Button* mac_create_button() { /* Mac按钮实现 */ }
TextBox* mac_create_textbox() { /* Mac文本框实现 */ }
DropDown* mac_create_dropdown() { /* Mac下拉框实现 */ }

UIFactory mac_factory = {
    .create_button = mac_create_button,
    .create_textbox = mac_create_textbox,
    .create_dropdown = mac_create_dropdown
};

关键点:抽象工厂确保你创建的产品之间是兼容的。你不会在Windows风格下拿到一个Mac的按钮——这在代码层面就给你保证了。

5.4 三种工厂的对比

模式 适用场景 复杂度 扩展性
简单工厂 产品种类少,变化不频繁 差(改switch-case)
工厂方法 产品种类多,需要灵活扩展 好(加子类即可)
抽象工厂 需要创建一组相关产品 好(加产品族)

5.5 知识体系图

工厂模式知识体系 简单工厂 工厂方法 抽象工厂 特点 • 一个函数创建所有对象 • 通过参数区分类型 • 扩展需修改工厂函数 特点 • 每个产品有自己的工厂 • 通过继承扩展 • 符合开闭原则 特点 • 创建一组相关对象 • 保证产品兼容性 • 适合产品族场景 适用场景 • 产品种类少 • 快速原型开发 • 小型项目 适用场景 • 产品种类多 • 需要灵活扩展 • 中型项目 适用场景 • 多平台移植 • UI组件库 • 大型项目

5.6 我的实战建议

我个人习惯是:从简单工厂开始,别过度设计。你想想看,如果项目就两三个产品类型,搞个抽象工厂那不是自找麻烦吗?

我在一个物联网网关项目里,最开始用简单工厂管理传感器驱动。后来设备类型从3种涨到12种,简单工厂的switch-case已经没法看了。这时候我才重构到工厂方法——每个传感器类型一个工厂文件,新增类型只需要加文件、注册工厂,老代码一行都不用改。

避坑指南:我曾经在抽象工厂里把不同产品族的对象混在一起创建,结果Windows风格下出现了Mac的文本框……调试了一整天。记住:抽象工厂的核心就是保证产品族内的一致性,千万别跨族调用。

最后说一句:C语言虽然没有class,但通过结构体+函数指针,完全能实现这些模式。别被语言限制了思路。


公众号:蓝海资料掘金营,微信deep3321