依赖注入(Dependency Injection 简写为DI)开发过程中解除耦合的经典手段,但是似乎从一开始这货就是为面向对象而生的,我所看到的示例都没有将C语言考虑在内。难道C语言不能使用这么经典的设计模式?本文就来介绍一下C语言如实使用依赖注入来解除耦合。
参数注入
对应于面向对象语言的构造函数注入,C语言作为过程语言,参数注入法是最简单、也是最直接的方法。最常见的排序方法qsort就是用这种方法:
可以看到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就可以了。
基于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语言中虽然这个结构体可以实例化,但是实例化后没有任何意义,其中的函数指针仍然无值可赋,所以我们要在另外的文件中实现这个接口:
上面实现一个利用本地文件存储数据的缓存方法,为了更变的使用这些实现,我们需要提供统一的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_create的type参数即可。
总结:
联系客服