依赖倒置原则要求高层与低层模块都依赖抽象,抽象不依赖细节;Java中通过接口定义契约并注入实现类来实现,如OrderService依赖NotificationService接口而非具体实现,支持灵活替换通知方式。

依赖倒置原则(DIP)的核心是:高层模块不应依赖低层模块,二者都应依赖抽象;抽象不应依赖细节,细节应依赖抽象。在 Java 中,最常用、最直接的实现方式就是通过 接口 来定义抽象契约,让具体类去实现它,再通过构造器、setter 或方法参数注入实现类对象。
用接口定义行为契约,隔离变化
接口天然代表“做什么”,不关心“怎么做”。比如业务中需要发送通知,可以先定义:
public interface NotificationService {
void send(String message, String recipient);
}
这样,调用方(如订单服务)只依赖这个接口,完全不知道是发邮件、短信还是站内信。后续新增微信通知?只需新增一个 WeChatNotificationService 实现该接口,无需修改原有逻辑。
高层模块通过接口编程,不 new 具体实现
违反 DIP 的写法是直接 new 实例:
立即学习“Java免费学习笔记(深入)”;
// ❌ 错误:订单服务硬编码依赖了 EmailService
public class OrderService {
private EmailService emailService = new EmailService();
public void processOrder(Order order) {
// ...
emailService.send("...", "..."); // 无法替换为其他通知方式
}
}
正确做法是把接口作为成员变量,并通过外部传入:
// ✅ 正确:依赖 NotificationService 接口
public class OrderService {
private final NotificationService notification;
// 构造注入 —— 最推荐的方式
public OrderService(NotificationService notification) {
this.notification = notification;
}
public void processOrder(Order order) {
// ...
notification.send("订单已创建", order.getCustomerEmail());
}
}
运行时绑定具体实现,靠依赖注入或工厂
接口本身不能实例化,必须在运行时提供真实对象。常见方式有:
- 手动传入:测试时直接 new 实现类传给构造器
- Spring 等框架自动注入:加
@Autowired或@Resource注解,容器根据类型匹配并注入对应 Bean - 简单工厂:由工厂类根据配置返回不同实现,如
NotificationFactory.create("sms")
关键点在于:谁创建、谁决定用哪个实现,应该和业务逻辑分离——这正是 DIP 要求的“控制反转”(IoC)基础。
注意接口粒度和稳定性
接口不是越多越好,也不是越通用越好。要满足:
-
单一职责:一个接口只定义一类行为(如
PaymentService不该混入日志方法) - 稳定抽象:接口方法签名尽量少变,否则所有实现类都要改;可考虑用默认方法兼容演进
- 面向使用者设计:接口由高层模块的需求驱动,而不是照着某个实现反推出来
比如,如果只有订单服务用通知,就定义 NotificationService;若用户中心也要发消息,可进一步抽象为 MessageService,但前提是两者确实需要统一语义和行为边界。


















