依赖注入(Dependency Injection)
依赖注入(Dependency Injection,简称 DI)是一种设计模式,也是实现控制反转(Inversion of Control,IoC) 的主要方式之一。它的核心思想是将对象依赖关系的创建和管理从对象内部转移到外部容器,从而降低代码耦合度,提高可测试性和可维护性。
核心概念拆解
-
依赖(Dependency):
-
指一个对象(A)完成其功能需要使用的另一个对象(B)。
-
例如:一个
OrderService
(订单服务)类在保存订单时可能需要一个OrderRepository
(订单存储库)类来访问数据库。OrderRepository
就是OrderService
的一个依赖。
-
-
注入(Injection):
-
指不是由对象(A)自己内部去创建(如
new B()
)或查找它的依赖(B),而是由外部实体(通常是框架或专门的容器)创建好依赖(B)的实例,并通过某种方式(构造器、Setter方法、接口等)传递(“注入”)给对象(A)。
-
为什么需要依赖注入? (解决什么问题?)
-
紧耦合(Tight Coupling): 传统方式下,如果
OrderService
在内部直接new OrderRepository()
,这两个类就紧密耦合在一起。 -
带来的问题:
-
难以测试: 想单独测试
OrderService
的逻辑(不涉及真实的数据库操作)非常困难,因为它内部硬编码了真实的OrderRepository
。 -
难以修改: 如果想更换数据库实现(比如从 MySQL 换到 PostgreSQL,或者换成内存数据库用于测试),必须修改
OrderService
的源代码。 -
难以复用:
OrderService
因为依赖具体的OrderRepository
实现,复用性降低。 -
职责不清:
OrderService
不仅要处理业务逻辑,还要负责创建其依赖对象,违反了单一职责原则。
-
依赖注入如何解决这些问题?
-
解耦(Decoupling): DI 将对象的创建和使用分离。
OrderService
不再负责创建OrderRepository
,它只需要声明它需要某个OrderRepository
(通常通过接口)。 -
控制反转(IoC): 创建依赖对象的控制权从业务对象 (
OrderService
) 反转到了外部容器(IoC Container)。(非常重要) -
外部配置: 外部容器负责读取配置(XML、注解、代码配置等),实例化所有需要的对象,并将它们组装(“注入”) 到需要它们的对象中。
依赖注入的常见方式
构造函数注入(Constructor Injection):
-
通过类的构造函数传递依赖项。这是最推荐的方式,因为它强制要求依赖在对象创建时就可用,且依赖项通常声明为
final
(不可变),保证了线程安全性和对象完整初始化。 -
示例:
public class OrderService {private final OrderRepository repository; // 通过接口声明依赖// 依赖通过构造函数注入public OrderService(OrderRepository repository) {this.repository = repository;}public void placeOrder(Order order) {// 使用注入的 repository 保存订单repository.save(order);}
}
Setter 方法注入(Setter Injection):
-
通过类的 Setter 方法 传递依赖项。对象创建后依赖关系可以改变(灵活性更高,但也可能引入状态不一致问题)。
-
示例:
public class OrderService {private OrderRepository repository; // 通过接口声明依赖// Setter 方法用于注入依赖public void setRepository(OrderRepository repository) {this.repository = repository;}public void placeOrder(Order order) {repository.save(order);}
}
接口注入(Interface Injection)(较少用):
依赖项通过一个专门的接口定义的方法来注入。这种方式相对复杂,现代框架中使用较少。
-
示例:
public interface RepositoryAware {void setRepository(OrderRepository repository);
}public class OrderService implements RepositoryAware {private OrderRepository repository;@Overridepublic void setRepository(OrderRepository repository) {this.repository = repository;}// ...
}
依赖注入容器(DI Container / IoC Container)
-
负责实现依赖注入机制的框架组件。
-
主要职责:
-
创建对象: 实例化应用程序中的组件(Beans)。
-
管理依赖: 根据配置,自动将依赖项注入到需要它们的组件中。
-
管理生命周期: 管理对象的创建、初始化、销毁等生命周期事件。
-
提供配置: 提供配置依赖关系的方式(XML、注解如
@Autowired
,@Inject
,@Resource
, Java Config 等)。
-
-
流行框架: Spring Framework(Java)、Guice(Java)、Dagger(Java/Android)、Angular(TypeScript/前端)、.NET Core DI(C#)等。
依赖注入的优点总结
-
降低耦合度: 组件只依赖于抽象(接口),不依赖于具体实现。
-
提高可测试性: 可以轻松地用 Mock 对象或 Stub 对象(实现相同接口)替换真实的依赖进行单元测试。
-
提高可维护性: 代码更清晰、更模块化。修改一个组件的实现或其依赖项变得更容易,影响范围更小。
-
提高可复用性: 解耦后的组件更容易被复用在不同的上下文中。
-
简化配置管理: 容器集中管理对象创建和依赖关系,配置更清晰。
-
促进良好设计: 鼓励面向接口编程和单一职责原则。
简单生活化比喻
想象你要喝咖啡:
-
没有 DI(紧耦合): 你自己种咖啡豆、烘焙、研磨、烧水、冲泡... 你需要知道并管理所有环节。
-
使用 DI: 你去咖啡馆(容器)。你只需要说“我要一杯拿铁”(声明你需要什么)。咖啡馆(容器)内部有咖啡机(依赖项)、咖啡师(负责组装)、牛奶等原料。咖啡师将制作好的拿铁(注入依赖)递给你。你不需要关心咖啡机是什么牌子、牛奶从哪里来(解耦)。如果你想换美式咖啡(更改依赖),只需要告诉咖啡馆换一种(更改配置),不用自己动手改造咖啡机。
总结
依赖注入是一种通过将对象的依赖关系交由外部容器管理和注入来实现解耦的设计模式。它极大地提高了代码的可测试性、可维护性、可复用性和灵活性,是现代软件开发(尤其是大型应用和框架)中必不可少的基础技术。依赖注入容器是实现这一模式的强大工具。
下一篇:
DI是IoC的关系-CSDN博客
DI的实现框架: Hilt -> Android 专属依赖注入(DI)框架-CSDN博客