设计模式之依赖倒置 C语言版本

本文介绍了依赖倒置原则(DIP),如何从直白的代码依赖底层模块转变为通过接口和模块化设计降低风险。通过C和C++示例展示了不同版本的代码优化,从基础实现到利用链接器固定模块范围,以确保添加或删除模块时对上层影响最小。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

  1. 什么是依赖倒置
    依赖倒置原则,英文缩写DIP,全称Dependence Inversion Principle。

    原始定义:High level modules should not depend upon low level modules. Both should depend upon abstractions. Abstractions should not depend upon details. Details should depend upon abstractions。
    官方翻译:上层模块不应该依赖底层模块,两者都应该依赖其抽象;抽象不应该依赖细节,细节应该依赖抽象。
    也就是要依赖确定的东西,对于软件,确定的东西就是抽象就是接口。

    代码开发时,从现实逻辑出发:上层依赖底层各个模块,代码自然就会写成上层依赖底层的样子。这样虽然很直白,但是底层代码变化很可能会引起上层代码的被动变化,而修改的越多,bug的可能性就越大。
    如果我们将上层依赖底层的关系倒置:底层依赖上层,接口的概念就出现了。

    C++的倒置实例

    下面通过一个模拟题目来实践一下依赖倒置原则
    业务有个固定的初始化流程顺序:sys->driver->memory 请模拟实现一下
    以下代码仅做示意

  2. C语言依赖倒置的应用–V1.0
    直白的功能实现

    // sys.c drive.c memory.c
    u32 sys_init(void) {}
    u32 drive_init(void) {}
    u32 memory_init(void) {}
    
    // init.c
    #include "sys.c"
    #include "drive.c"
    #include "memory.c"
    
    void init(void) {
    	sys_init();
    	drive_init();
    	memory_init();
    }
    

    代码很清晰,但是如果需要新增其他流程或者需要将某一现有流程删除,
    需要做的就是新增或删除1个.c并且对应修改init函数。当项目变得复杂,模块众多的时候,修改init函数甚至修改init.c文件的动作都充满了风险。

  3. C语言依赖倒置的应用–V1.1 降低风险
    通过新增init_modify.c暴露部分原有init.c的功能,来实现对上层尽可能小的影响:仅需修改init_modify.c

    // sys.c drive.c memory.c
    u32 sys_init(void) {}
    u32 drive_init(void) {}
    u32 memory_init(void) {}
    
    // init.c
    #include "init_modify.h"
    
    void init(void) {
    	for (s32 i = 0; i < sizeof(fun_list)/sizeof(fun_list[0]; i++) {
    		fun_list[i]();
    	}
    }
    
    // init_modify.c
    #include "sys.c"
    #include "drive.c"
    #include "memory.c"
    
    void (*fun_list[])() = {
    	sys_init,
    	drive_init,
    	memory_init,
    }
    

    接口抽象仍然不足,仍有底层修改影响上层的风险

  4. C语言依赖倒置的应用–V2.0
    如下实现可以保证在新增或删除模块时,仅通过新增删除对应.c即可,完全不用修改上层文件init.c
    原理就是利用链接器可以将数据固定在指定范围内。
    缺点也很明显:不易理解,代码量增加。

    // *.ld
    SECTIONS
    {
    	...
    	.module_init:
    	{
    		___module_init_start = .;
    		KEEP(*(.module_init))
    		__module_init_end = ,;
    		. = ALIGN(4);
    	} > DATA
    	...
    }
    
    // module_init.h
    typedef unsigned int u32
    typedef signed int   s32
    
    typedef u32 (*initFunc)(void);
    
    struct module_init_para {
    	u32 module_level;
    	initFunc func;
    }
    
    enum module_init_level {
    	MODULE_SYS_LEVEL  		 = 0,
    	MODULE_DRIVE_LEVEL		 = 1
    	MODULE_MEMORY_LEVEL		 = 2,	
    };
    
    // sys.c
    u32 sys_init(void) {}
    static struct module_init_para m_sys = {MODULE_SYS_LEVEL, sys_init};
    static void *p_m_sys __used __attribute__(section(".module_init")) = &m_sys;
    
    // init.c
    extern u32 __module_init_start;
    extern u32 __module_init_end;
    
    static void _swap(struct module_init_para **a, struct module_init_para **b)
    {
    	struct module_init_para *tmp;
    	tmp = *a;
    	*a = *b;
    	*b = tmp;
    }
    
    void sort_modules(void) {
    	struct module_init_para **p = (struct module_init_para **)&__module_init_start;
    	s32 cnt = &__module_init_start - &__module_init_end;
    	s32 i ,j;
    
    	for (i = 0; i < cnt - 1; i++) {
    		for (j = i + 1; j < cnt; j++) {
    			if (p[i]->level > p[j]->level)
    				_swap(&p[i], &p[j]);
    		}
    	}
    }
    
    void start_module_init(void) {
    	struct module_init_para **p;
    	sort_modules();
    	
    	for (p = (struct module_init_para **)&__module_init_start;
    		 p != (struct module_init_para **)&__module_init_end;
    		 p++) {
    		(*p)->func();
    	}
    }
    
    void init(void) {
    	start_module_init();
    }
    
  5. C语言依赖倒置的应用–V2.1
    进一步优化

    // module_init.h
    #define MODULE_INIT(mLevel, function)	\
    static struct module_init_para m_##function = {	\
    	.module_level = mLevel,	\
    	.func = function,	\
    };	\
    static void *p_##function __used __attribute__(section(".module_init")) = {&m_##function}
    
    // sys.c
    u32 sys_init(void) {}
    module_INIT(MODULE_SYS_LEVEL, sys_init);
    
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值