5-软件设计原则详解-依赖倒置原则
1. 依赖倒置原则 (Dependency Inversion Principle, DIP)
1. 定义:
- 高层模块不应该依赖于低层模块,两者都应该依赖于抽象
- 抽象不应该依赖于细节,细节应该依赖于抽象
2. 解释:
- 通过抽象(接口或抽象类)进行解耦
- 使用依赖注入等技术实现
- 提高代码的可测试性和灵活性
3. 关键点:
- 传统的依赖关系(如分层架构中)是自上而下的,高层模块直接调用低层模块,导致紧耦合。
- DIP通过引入抽象(接口或抽象类)将依赖关系“倒置”,使高层和低层模块都依赖抽象,而非具体实现。
4. 什么是“倒置”?
传统依赖关系(未倒置)
-
高层模块直接依赖低层模块:
高层模块 → 低层模块
问题:低层模块的改动会直接影响高层模块(例如修改数据库访问代码会导致业务逻辑变动)。
倒置后的依赖关系
-
高层和低层模块均依赖抽象:
高层模块 → 抽象 ← 低层模块
效果:
- 高层模块不再直接依赖低层模块,而是通过抽象间接调用。
- 低层模块需要实现抽象接口,从而“依赖”抽象。
- 依赖方向从“自上而下”变为“双向依赖抽象”,这就是“倒置”的含义。
5. 高层模块 vs 低层模块
高层模块(High-Level Module)
- 定义:包含核心业务逻辑、应用策略的模块,通常解决复杂问题。
- 特点:
- 关注“做什么”(What),而非“如何做”(How)。
- 例如:订单处理系统、支付流程控制器。
- 传统误区:高层模块直接调用低层模块的具体实现(如
*Impl
类)。
低层模块(Low-Level Module)
- 定义:实现具体技术细节的模块,通常是一些基础服务或工具。
- 特点:
- 关注“如何做”(How),例如数据库访问、文件IO、网络通信。
- 例如:MySQL数据库操作、Redis缓存实现。
- 传统误区:低层模块的设计直接影响高层模块。
6. 实际应用场景
- 插件式架构:通过抽象允许动态扩展功能(如IDE的插件系统)。
- 单元测试:高层模块通过抽象接口可轻松Mock低层模块。
- 框架设计:框架定义抽象接口,用户提供具体实现(如Spring的
DataSource
)。
通过DIP,系统的核心逻辑(高层模块)不再受技术细节(低层模块)的束缚,从而更易于扩展和维护。
示例:
// 违反DIP
class LightBulb {
public void turnOn() { /* ... */ }
public void turnOff() { /* ... */ }
}
class Switch {
private LightBulb bulb;
public Switch(LightBulb bulb) {
this.bulb = bulb;
}
public void operate() {
// 直接依赖具体实现
bulb.turnOn();
}
}
// 遵循DIP
interface Switchable {
void turnOn();
void turnOff();
}
class LightBulb implements Switchable {
public void turnOn() { /* ... */ }
public void turnOff() { /* ... */ }
}
class Fan implements Switchable {
public void turnOn() { /* ... */ }
public void turnOff() { /* ... */ }
}
class Switch {
private Switchable device;
public Switch(Switchable device) {
this.device = device;
}
public void operate() {
device.turnOn();
}
}