
依赖倒置原则(DIP)不是加一层接口就完事,关键在于用抽象切断高层对具体实现的硬编码绑定。它解决的不是“能不能换”,而是“换的时候动不动代码”——变量声明、对象创建、模块调用,只要写死具体类型,就是隐患。
变量声明必须用接口或抽象类型
硬编码常藏在最平常的变量定义里。比如:
❌ 错误写法:LED_Driver_STM32 driver; —— 类型直接指向具体驱动,后续想换GD32或ESP32就得改所有声明✅ 正确写法:LED_Interface *driver; —— 变量类型是抽象接口指针,实际指向哪个实现由初始化决定
这一步看似微小,却决定了整个模块是否可插拔。C语言中用函数指针结构体模拟接口,C++/Java则直接用interface或abstract class。
对象创建不能出现在业务逻辑中
业务函数里出现new AudioDecoder()或HAL_GPIO_Init(),等于把硬件细节焊进了控制流程。
❌ 紧耦合示例:void start_alarm() { buzzer = new STM32_Buzzer(); buzzer->on(); }✅ 解耦做法:把实例创建移出业务层,通过参数传入或工厂返回。例如:void start_alarm(Buzzer_Interface *buzzer) { buzzer->on(); }
这样,同一段告警逻辑,既可用蜂鸣器,也可用LED闪烁,甚至发网络通知,只需换传入对象,不碰业务代码。
依赖注入是落地的关键动作
光有接口不够,得让具体实现“进得来”。常见三种注入方式:
构造注入:在模块初始化时,把具体对象传给构造函数(如AlarmSystem(led_driver, buzzer_driver))设值注入:提供setter方法,在运行时动态替换(适合配置切换或测试模拟)全局注册表(嵌入式常用):启动时将各驱动注册到统一管理器,业务层按名称获取抽象句柄
没有注入,抽象就只是摆设;注入到位,才能真正实现“编译一次,多平台部署”。
接口设计要聚焦契约,而非实现细节
一个糟糕的接口会暴露底层信息,反而加重耦合。比如:
❌ 不推荐:void gpio_set_pin(GPIO_TypeDef* port, uint16_t pin); —— 把STM32寄存器类型暴露给上层✅ 推荐:void led_turn_on(Led_Id id); 或 void output_write(Output_Channel ch, bool state);
接口应描述“做什么”,而不是“怎么做的”。ID、通道名、状态语义,比寄存器地址更稳定、更易理解、更易迁移。

评论(0)