设计模式 - 反转原则:DIP(Dependence Inversion Principle)最佳实践
文章目录
- 引言
- 1. DIP 的核心概念与现实类比
- 2. DIP 的原始定义拆解
- 3. 为什么要使用 DIP?
- 4. 实践演示:四种设计演化
- 设计一:完全耦合
- 设计二:仅高层接口化(常见误区)
- 设计三:低高双向抽象(实现 DIP)
- 设计四:分包与框架注入
- 5. 扩展:IoC、DI、IoC 容器 与 DIP 的区别
引言
在设计模式 - 面向对象原则:SOLID最佳实践我们了解了 SOLID 五大原则,但其中“依赖反转原则(DIP)”常被误用甚至混淆。
实际上,在项目中或许已经在践行它,只是没有深入理解其“反转控制”的真意。接下来将从现实类比到原理拆解,再到代码演化示例,彻底搞清 DIP,让它在开发工作中发挥更大作用。
1. DIP 的核心概念与现实类比
-
现实类比:电商时代交易
-
强耦合:买家当面付款 → 卖家交货,双方必须见面,耦合度高
-
依赖反转:银行担保模型
- 买家先将钱付给银行
- 银行通知卖家发货
- 买家验货无误后,银行再支付给卖家
-
原理:买卖双方不再直接依赖彼此,而是共同依赖“银行”这一标准化中介接口
-
-
软件类比:浏览器与 Web 服务器
- 浏览器(高层组件)不直接依赖某个特定 Web 服务器实现(低层组件),只依赖 HTTP 协议(抽象)。
- 只要服务端遵循 HTTP 标准,就能与任意浏览器交互。
2. DIP 的原始定义拆解
“高级组件不应依赖于低级组件,两者都应依赖抽象;抽象不应该依赖实现,实现应该依赖抽象。”
-
高级 vs 低级组件:
- 按调用层级区分,不代表功能复杂度高低。
- 例:油门(高级)调用引擎(低级);应用程序(高层)调用操作系统(低层)。
-
依赖抽象的意义:
- 消除组件变化对彼此的影响,保证双方只能在抽象约束范围内变动。
- 如油门形状或引擎材质变化,都不影响“加减油”这一抽象接口。
3. 为什么要使用 DIP?
-
控制代码变化影响范围
- 当外部系统需求变更,若双方通过统一抽象接口通信,只要接口不变,内部实现可随意调整,无需改动调用方代码。
-
提升可读性和可维护性
- 统一抽象让职责清晰:相同功能集中定义在接口所在处,维护者无需跳转到各处定制逻辑,阅读和扩展更加顺畅。
4. 实践演示:四种设计演化
设计一:完全耦合
public class StringProcessor {private final StringReader stringReader; // 具体类private final StringWriter stringWriter; // 具体类public StringProcessor(StringReader stringReader, StringWriter stringWriter) {this.stringReader = stringReader;this.stringWriter = stringWriter;}public void readAndWrite() {stringWriter.write(stringReader.getValue());}public static void main(String[] args) {StringReader sr = new StringReader();sr.read("1111111");StringWriter sw = new StringWriter();StringProcessor sp = new StringProcessor(sr, sw);sp.readAndWrite();}
}
- 优点:逻辑直接,编码简单
- 缺点:高度耦合,低级组件修改必然影响高级组件
设计二:仅高层接口化(常见误区)
public interface StringProcessor {void readAndWrite(StringReader stringReader, StringWriter stringWriter);
}public class StringProcessorImpl implements StringProcessor {@Overridepublic void readAndWrite(StringReader sr, StringWriter sw) {sw.write(sr.getValue());}
}
- 改进:高层依赖抽象
- 不足:低层依然是具体类,变化仍影响高级组件,未实现真正依赖反转
设计三:低高双向抽象(实现 DIP)
public interface StringReader {void read(String input);String getValue();
}public interface StringWriter {void write(String value);
}public class StringProcessorImpl implements StringProcessor {@Overridepublic void readAndWrite(StringReader sr, StringWriter sw) {sw.write(sr.getValue());}
}// 客户端
StringReader sr = new StringReaderImpl();
sr.read("333333");
StringWriter sw = new StringWriterImpl();
StringProcessor sp = new StringProcessorImpl();
sp.readAndWrite(sr, sw);
- 特点:高级、低级组件均依赖抽象,控制权真正“反转”到接口层
设计四:分包与框架注入
public class SPTest {@Resourceprivate StringProcessor sp;@Resourceprivate StringReader sr;@Resourceprivate StringWriter sw;public void run() {sr.read("444444");sp.readAndWrite(sr, sw);}
}
- 改进:接口与实现可分布在不同包或库中,使用 Spring 等 IoC 容器自动注入,进一步提升复用与解耦
5. 扩展:IoC、DI、IoC 容器 与 DIP 的区别
术语 | 定义 | 与 DIP 的关系 |
---|---|---|
IoC (控制反转) | 设计原则,将对象创建与依赖控制交由第三方完成 | 原理层面与 DIP 类似,关注“谁来控制” |
DI (依赖注入) | 实现 IoC 的设计模式,将依赖通过构造/属性/方法注入到目标类中 | DIP 的具体实现手段 |
IoC 容器 (DI 容器) | 管理对象创建与生命周期、自动注入依赖的框架(如 Spring) | 支撑 DI 实现,帮助落地 DIP 与 IoC |
DIP (依赖反转原则) | 高层不依赖低层,抽象不依赖实现;实现依赖抽象 | 指导如何设计抽象层与依赖关系的顶层设计原则 |