您现在的位置是:首页 > 文章详情

如何写好C代码之依赖注入

日期:2019-01-25点击:320

依赖注入(Dependency Injection 简写为DI)开发过程中解除耦合的经典手段,但是似乎从一开始这货就是为面向对象而生的,我所看到的示例都没有将C语言考虑在内。难道C语言不能使用这么经典的设计模式?本文就来介绍一下C语言如实使用依赖注入来解除耦合。

参数注入

对应于面向对象语言的构造函数注入,C语言作为过程语言,参数注入法是最简单、也是最直接的方法。最常见的排序方法qsort就是用这种方法:

void qsort(void* base, size_t num, size_t size, int (*compar)(const void*,const void*)); 

可以看到qsort函数的第四个参数compar就是外部依赖的对象(函数),因为不同场景有不同的比较元素大小的方式,通过参数将外部依赖注入,使该函数更加具有通用型,因为实际上我们用qsort,只是用他的排序算法,其他的都是和具体使用场景有关。

设置(set)接口注入

上一篇我们介绍的设置回调函数的方法其实就是使用这种方法,其本质就是专门对外提供一个接口,用来将依赖的外部对象或者函数注入到本模块中来。比如开发一个模块,需要申请内存,但是为了易用性,除了使用系统自带的内存申请函数,我们需要支持第三方的内存池模块来申请内存,我们就可以提供一个API来设置申请释放内存的函数,如下示例:

///默认申请内存方式为系统自带的函数 static void *(*malloc_function)(size_t size) = malloc; static void (*free_function)(void *p) = free; int sample_module_init() { return 0; } ///设置新的分配内存的函数 int sample_module_set_memory_api(void *(*get)(size_t size), void(*put)(void *p)) { malloc_function = get; free_function = put; return 0; } ///申请一个size大小的int类型数组 int *sample_module_create_int_array(size_t *size) { int *p = (int *)malloc_function(sizeof(int) * size); return p; } 

这样我们在使用这个模块时,就可以设置三方的内存申请方式了。比如想使用jemalloc的内存分配方式,调用sample_module_set_memory_api 将内存分配的函数指针设置为该库的内存申请API就可以了。

sample_module_set_memory_api(mallocx, freex); int *p = sample_module_create_int_array(2); 

基于Interface的注入

面向对象编程有一个接口(Interface)的概念。从概念上讲,接口是一个抽象类,代表一系列行为相同的类;从实现上来讲,接口就是一堆方法的集合。C语言虽然没有接口的的概念,但是完全可以实现接口的功能,通过结构体将一系列函数指针组合起来,就可以实现接口的功能。比如我们需要实现一个缓存功能模块,包括set_value和get_value两个方法,为了使模块更具有扩展性,我们先定义抽象接口

struct cache_interface { ///store kv in cache int (*set_value)(void *instance, const char *key, const char *value); ///find kv in cache int (*get_value)(void *instance, const char *key, char **value_out); }; 

在面向对象语言中,接口不能实例化。C语言中虽然这个结构体可以实例化,但是实例化后没有任何意义,其中的函数指针仍然无值可赋,所以我们要在另外的文件中实现这个接口:

///实现方式---本地文件缓存 struct cache_local_file { ///必须是第一个成员 struct cache_interface methods; char file_path[32]; FILE *fp; }; int set_value(struct cache_interface *instance, const char *key, const char *value) { struct cache_local_file *ins = (struct cache_local_file *)instance; fprintf(ins->fp, "%s:%s", key, value); return 0; } int get_value(struct cache_interface *instance, const char *key, const char **value_out) { *value_out = "sample data"; return 0; } //创建实例(创建一个实例,相当于构造函数) int cahce_local_file_create(const char *path, struct cache_interface **instance) { struct cache_local_file *ins = (struct cache_local_file *)malloc(sizeof(cache_local_file)); strncpy(ins->file_path, sizeof(ins->file_path), path); ins->fp = fopen(path); ins->methods.get_value = get_value; ins->methods.set_value = set_value; *instance = &(ins->methods); return 0; } 

上面实现一个利用本地文件存储数据的缓存方法,为了更变的使用这些实现,我们需要提供统一的API来共用户使用,这样当缓存的具体实现有变化时,使用缓存的用户不用大规模修改代码,甚至不用修改代码。下面我们类似于面向对象里的工厂模式来提供这些API。

struct cache_implement { char name[32], int (*create)(const char *input, struct cache_interface **instance); } struct cache_implement impl[32] = {0}; void init() { strncpy(impl[0].name, sizeof(impl[0].name), "local_file"); impl[0].create = cahce_local_file_create; ///more implement to add }; int cache_create(const char *type, const char *param, struct cache_interface **ins) { for(int i =0 ; i < 32; i++) { if (0 == strcmp(type, impl[i].name)) { impl[i].create(type, param, ins); } } return 0; } int cache_set_value(struct cache_interface *ins, const char *key, const char *value) { ///another user code ///set kv return ins->set_value(ins, key, value); } int cache_get_value(struct cache_interface *ins, const char *key, const cahr **value_out) { ///another user code; ///get kv return ins->get_value(ins, key, value); } 

这样的话,使用者就只关注到三个API,如果要变换不同的缓存实现(比如使用redis或者memcache存储数据),只要修改cache_createtype参数即可。

总结:

  • 参数注入:适合简单函数的场景,一般如果单个函数(不属于任何模块的工具式函数)如果需要调用外部的API,可以试着用注入的方法。
  • 设置接口注入:适合运行时设置一个模块调用的外部API。
  • interface注入:适合将一个模块的一组API一起设置到摸个调用这个,是对一个模块的API的整个注入。
原文链接:https://my.oschina.net/sundq/blog/3006136
关注公众号

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。

持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。

转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。

文章评论

共有0条评论来说两句吧...

文章二维码

扫描即可查看该文章

点击排行

推荐阅读

最新文章