什么是工厂模式(C语言面向对象实现)
絮絮叨叨:今天师傅让我给他讲讲什么是工厂模式,工厂模式又要怎么用。
虽然说知道什么是工厂模式,但是在还没在实际的代码中用过。于是乎又深入的学习了下,发现工厂模式其实我们都见过,只是并没意识到而已。
什么是工厂模式
这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。在工厂模式中,我们在创建对象时不会对客户端暴露创建逻辑,并且是通过使用一个共同的接口来指向新创建的对象。[1]
按照我的理解就是:“工厂模式将创建对象和使用对象两个过程分离,对于使用者无需关心对象的产生过程,直接指定需要的对象即可使用该对象的方法”
举一个我们生活中实际的例子。
您需要一辆汽车,可以直接从工厂里面提货,而不用去管这辆汽车是怎么做出来的,以及这个汽车里面的具体实现。
一个栗子
在某一项目需要用到 EEPROM、Flash 这两个设备,分别保存不同数据。在使用常规写法是下面的代码。
///>> eeprom.c
#include <stdint.h>
#include <stdio.h>
#include <string.h>
void eeprom_init(void)
{
printf("初始化 EEPROM\n");
}
void eeprom_open(void)
{
printf("启用 EEPROM\n");
}
void eeprom_write(uint32_t addr, uint8_t *data, uint16_t len)
{
printf("向EEPROM 地址:%x 写入%d 字节数据:%s\n", addr, len, (char *)data);
}
///>> eeprom.h
void eeprom_init(void);
void eeprom_open(void);
void eeprom_write(uint32_t addr, uint8_t *data, uint16_t len);
///>> flash.c
void flash_init(void)
{
printf("初始化 FLASH\n");
}
void flash_open(void)
{
printf("启用 FLASH\n");
}
void flash_write(uint32_t addr, uint8_t *data, uint16_t len)
{
printf("向FLASH 地址:%x 写入%d 字节数据:%s\n", addr, len, (char *)data);
}
///>> flash.h
void flash_init(void)
void flash_open(void)
void flash_write(uint32_t addr, uint8_t *data, uint16_t len);
///>> main.c
#include "eeprom.h"
#include "flash.h"
int main(void)
{
char *str[] = {"我是EEPROM", "我是FLASH"};
eeprom_init();
eeprom_open();
flash_init();
flash_open();
eeprom_write(0x0100, str[0], strlen(str[0]));
flash_write(0x02000100, str[1], strlen(str[1]));
}
运行结果为:
初始化 EEPROM
启用 EEPROM
初始化 FLASH
启用 FLASH
向EEPROM 地址:100 写入12 字节数据:我是EEPROM
向FLASH 地址:2000100 写入11 字节数据:我是FLASH
在上面的应用代码中(main函数)直接使用了eeprom和flash相关的API(包含eeprom.h和flash.h),所以main 对eeprom、flash 产生了依赖。
如果后面嫌弃flash容量不够,而换成SD卡,那main 函数和flash相关的的API都需要更改,对于频繁更换的场景这是繁琐的。
使用工厂模式
使用工厂模式实现上述功能,代码需要怎么写呢?
工厂模式是使用一个工厂接口将其他类的所有创建初始化处理完成,对于应用程序无需关心创建的细节。
首先创建抽象出一个存储类,保存所有存储类信息到数组,然后创建工厂函数,在工厂函数中查找对应的实例,然后对它进行初始化。
我们保持上面的eeprom.c、flash.c 不变,创建一个factory.c和对应头文件。代码如下:
///>> factory.h
typedef struct storage
{
char *name;
void (*init)(void);
void (*open)(void);
void (*write)( uint32_t, uint8_t *, uint16_t);
} * storage_t;
storage_t storage_factory(char *name);
///>> factory.c
struct storage storage_list[] =
{
{"eeprom", eeprom_init, eeprom_open, eeprom_write},
{"flash", flash_init, flash_open, flash_write},
};
storage_t storage_factory(char *name)
{
int i;
for (i = 0; i < sizeof(storage_list) / sizeof(storage_list[0]); i++)
{
if (0 == strcmp(name, storage_list[i].name))
{
storage_list[i].init();
storage_list[i].open();
return &storage_list[i];
}
}
return NULL;
}
///>>main.c
#include "factory.h"
int main(void)
{
char *str[] = {"我是EEPROM", "我是FLASH"};
storage_t byte_data = storage_factory("eeprom");
storage_t sector_data = storage_factory("flash");
byte_data->write(0x0100, str[0], strlen(str[0]));
sector_data->write(0x02000100, str[1], strlen(str[1]));
}
在这一份代码中,main 、eeprom 和 flash 之间的耦合解除了,main.c 的依赖变成了factory 。运行结构依旧和上面相同。
而如果需要将flash 换成 sd 卡,则main 函数只需将 工厂创建时传递的名字改成sd,并在factory添加对应的处理操作方法即可。
身边的工厂模式
不知道有人对上面的第二个代码是否感到熟悉,在C语言的文件操作,linux 的设备驱动,都是使用类似的方式初始化并开启设备。
fprintf("filename","w");
open("led",O_WRONLY);
而对于使用者而言,我们并不需要对设备的初始化流程有了解,只需要使用 fopen\open 函数进行打开需要的文件名,其他过程在内部已经将这些完成。
所有从某种角度而言,open\fopen 函数就是一个工厂的入口,它将具体的设备初始化细节进行屏蔽。
后记
在上面的第一种方法,其实属于面向过程编程。
在第二种方法,使用结构体将eeprom和flash抽象成了一个存储类,变成了简单的面向对象编程。
在上面的过程中,依赖关系从main 依赖 eeprom
变成了 main 依赖 factory
,从而达到解耦的目的。或者还可以说,eeprom 通过 factory 向 main 中注入了依赖关系,这也就是依赖注入。
[2]
参考资料
工厂模式: 菜鸟教程
[2]设计模式中的工厂模式和依赖注入之间有什么关系: https://www.zhihu.com/question/279991129/answer/419915018
end
爱技术,爱生活
这里是非典型技术宅,点击上方蓝字关注
欢迎点击 在看👀,点赞👍,收藏⭐
微信公众号|非典型技术宅