依赖注入和工厂模式有什么关系

依赖注入和工厂模式有什么关系?(C语言示例)

絮絮叨叨:今天师傅让我给他讲讲什么是工厂模式,工厂模式又要怎么用。

虽然说知道什么是工厂模式,但是在还没在实际的代码中用过。于是乎又深入的学习了下,发现工厂模式其实我们都见过,只是并没意识到而已。


什么是工厂模式

这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
在工厂模式中,我们在创建对象时不会对客户端暴露创建逻辑,并且是通过使用一个共同的接口来指向新创建的对象。

按照我的理解就是:“工厂模式将创建对象和使用对象两个过程分离,对于使用者无需关心对象的产生过程,直接指定需要的对象即可使用该对象的方法

举一个我们生活中实际的例子。

您需要一辆汽车,可以直接从工厂里面提货,而不用去管这辆汽车是怎么做出来的,以及这个汽车里面的具体实现。

一个栗子

在某一项目需要用到 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 的设备驱动,都是使用类似的方式初始化并开启设备。

而对于使用者而言,我们并不需要对设备的初始化流程有了解,只需要使用 fopen\open 函数进行打开需要的文件名,其他过程在内部已经将这些完成。

所有从某种角度而言,open\fopen 函数就是一个工厂的入口,它将具体的设备初始化细节进行屏蔽。

后记

在上面的第一种方法,其实属于面向过程编程。

在第二种方法,使用结构体将eeprom和flash抽象成了一个存储类,变成了简单的面向对象编程。

在上面的过程中,依赖关系从main 依赖 eeprom 变成了 main 依赖 factory ,从而达到解耦的目的。
或者还可以说,eeprom 通过 factory 向 main 中注入了依赖关系,这也就是依赖注入


依赖注入和工厂模式有什么关系
https://gary-hobson.github.io/2022/09/04/依赖注入和工厂模式有什么关系/
作者
非典型技术宅
发布于
2022年9月4日
许可协议