跳到主要内容

依赖倒置原则

什么是依赖倒置原则

依赖倒置原则 (Dependency Inversion Principle, DIP) 是 SOLID 原则中的最后一个原则,由 Robert Martin 提出。其核心思想是:

高层模块不应该依赖低层模块,两者都应该依赖抽象。
抽象不应该依赖具体实现细节,具体实现细节应该依赖抽象。

依赖倒置原则的目的是降低高层模块对低层模块的依赖,减少代码的耦合性,增强系统的可维护性和灵活性。

高层模块与低层模块

在依赖倒置原则中:

  • 高层模块:指调用者,通常负责复杂的业务逻辑
  • 低层模块:指被调用者,通常负责基础操作,如数据访问、网络通信等
  • 抽象:指接口或抽象类,用于定义规范,不包含具体实现

在传统开发中,高层模块直接依赖低层模块,形成了从上到下的依赖关系。而依赖倒置原则要求反转这种依赖关系,让高层模块和低层模块都依赖抽象,从而形成了依赖"倒置"。

示例一:消息发送系统

不符合依赖倒置原则的设计

public class Notification {
private MessageSender messageSender;

public Notification() {
// 直接依赖具体实现
this.messageSender = new MessageSender();
}

public void sendMessage(String cellphone, String message) {
this.messageSender.send(cellphone, message);
}
}

public class MessageSender {
public void send(String cellphone, String message) {
// 具体的短信发送实现
}
}

// 使用方式
Notification notification = new Notification();
notification.sendMessage("13912345678", "验证码:1234");

符合依赖倒置原则的设计

// 定义抽象接口
public interface MessageSender {
void send(String cellphone, String message);
}

// 具体实现类
public class SmsSender implements MessageSender {
@Override
public void send(String cellphone, String message) {
// 发送短信的具体实现
}
}

public class EmailSender implements MessageSender {
@Override
public void send(String cellphone, String message) {
// 发送邮件的具体实现
}
}

// 高层模块依赖抽象接口
public class Notification {
private MessageSender messageSender;

// 通过构造函数注入依赖
public Notification(MessageSender messageSender) {
this.messageSender = messageSender;
}

public void sendMessage(String cellphone, String message) {
this.messageSender.send(cellphone, message);
}
}

// 使用方式
MessageSender smsSender = new SmsSender();
Notification notification = new Notification(smsSender);
notification.sendMessage("13912345678", "验证码:1234");

在符合依赖倒置原则的设计中:

  1. Notification(高层模块)不再直接依赖SmsSender(低层模块)
  2. 两者都依赖MessageSender接口(抽象)
  3. 依赖的方向"倒置"了 - 低层模块实现了高层模块定义的接口

示例二:框架与应用

在实际应用中,依赖倒置原则经常用于指导框架层面的设计。

例如,Tomcat 作为 Java Web 应用的容器:

  • Tomcat 是高层模块
  • 用户编写的 Web 应用是低层模块
  • 两者都依赖 Servlet 规范(抽象)

不是 Tomcat 依赖用户的应用,而是 Tomcat 和用户应用都依赖 Servlet 规范。用户应用实现 Servlet 接口,Tomcat 通过这些接口调用应用程序,这就是依赖的"倒置"。

依赖倒置与相关概念的关系

依赖倒置原则 vs 控制反转

  • 控制反转 (Inversion of Control, IoC) 是一种设计思想,它将程序的执行流程控制权从程序员反转给框架。
  • 控制反转是一个宽泛的概念,可以通过多种方式实现,包括依赖注入、模板方法模式等。
  • 依赖倒置原则是一种设计原则,指导如何设计类与类之间的依赖关系。
  • 控制反转是依赖倒置原则的一种应用方式。

依赖倒置原则 vs 依赖注入

  • 依赖注入 (Dependency Injection, DI) 是一种编程技巧,它不在类内部通过 new 创建依赖对象,而是从外部传入依赖对象。
  • 依赖注入是实现依赖倒置原则的一种手段。
  • 依赖注入强调"如何传递依赖",依赖倒置原则强调"依赖什么"。

依赖注入框架

依赖注入框架(如 Spring)是自动化依赖注入过程的工具,它可以:

  1. 自动创建对象
  2. 管理对象的生命周期
  3. 自动进行依赖注入

使用依赖注入框架可以简化依赖倒置原则的实现,使开发者专注于业务逻辑。

为什么需要依赖倒置

  1. 降低耦合度:通过依赖抽象而非具体实现,降低了模块间的耦合度
  2. 提高可扩展性:可以轻松替换底层模块的实现,而无需修改高层模块
  3. 提高可测试性:可以轻松使用 Mock 对象替代真实依赖,便于单元测试
  4. 提高代码稳定性:抽象接口变化的频率远低于具体实现,依赖抽象可以减少变化带来的影响

实践建议

  1. 高层模块定义抽象接口,低层模块实现这些接口
  2. 使用依赖注入的方式传递依赖
  3. 优先使用构造函数注入,确保核心依赖不可变
  4. 对于可选依赖,可以使用 setter 注入
  5. 考虑使用依赖注入框架简化对象创建和依赖管理
  6. 接口设计要保持稳定,避免频繁变更

总结

依赖倒置原则是 SOLID 原则中的重要一环,它通过反转传统的依赖关系,使得高层模块和低层模块都依赖于抽象,从而降低系统耦合度,提高系统的可维护性和灵活性。

依赖倒置原则与控制反转、依赖注入有密切关系:

  • 依赖倒置原则是一种设计原则,指导"依赖什么"
  • 控制反转是一种设计思想,实现程序控制流的反转
  • 依赖注入是一种编程技巧,解决"如何传递依赖"
  • 依赖注入框架是自动化依赖注入的工具