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 知识体系图
5.6 我的实战建议
我个人习惯是:从简单工厂开始,别过度设计。你想想看,如果项目就两三个产品类型,搞个抽象工厂那不是自找麻烦吗?
我在一个物联网网关项目里,最开始用简单工厂管理传感器驱动。后来设备类型从3种涨到12种,简单工厂的switch-case已经没法看了。这时候我才重构到工厂方法——每个传感器类型一个工厂文件,新增类型只需要加文件、注册工厂,老代码一行都不用改。
避坑指南:我曾经在抽象工厂里把不同产品族的对象混在一起创建,结果Windows风格下出现了Mac的文本框……调试了一整天。记住:抽象工厂的核心就是保证产品族内的一致性,千万别跨族调用。
最后说一句:C语言虽然没有class,但通过结构体+函数指针,完全能实现这些模式。别被语言限制了思路。
公众号:蓝海资料掘金营,微信deep3321