文章目录
- 1. 核心概念
- 2. 依赖注入的方式
- 3. 构造函数注入
- 3.1 构造函数注入的优点包括:
- 4. 设值注入
- 4.1 设值注入的特点和优点
- 4.2 使用设值注入的注意事项
- 5. 接口注入
- 5.1 接口注入的基本概念:
- 5.2 使用接口注入的注意事项:
依赖注入(Dependency Injection,DI)
1. 核心概念
是面向对象编程中的一种设计模式,它主要用于降低组件之间的耦合度,促进代码的可测试性和可维护性。在依赖注入中,对象的依赖关系不是在内部创建,而是由外部容器(比如 Spring 框架)在对象创建时将依赖对象注入进来。
-
依赖:一个对象依赖于另一个对象,如果它需要使用另一个对象的服务或者资源来完成自身的功能。例如,一个订单服务可能依赖于一个支付服务来完成支付功能。
-
注入:指的是将一个对象的依赖关系传递给另一个对象的过程。依赖注入有三种主要的实现方式:
2. 依赖注入的方式
- 构造函数注入(Constructor Injection):依赖通过对象的构造函数传入。
- 设值注入(Setter Injection):依赖通过对象的设值方法(setter 方法)传入。
- 接口注入(Interface Injection):依赖通过特定的接口方法传入。
- 控制反转(Inversion of Control,IoC):依赖注入通常与控制反转紧密相关。控制反转是一种设计原则,指的是将对象的创建、管理和关联的控制权从应用程序代码中反转到外部容器(比如 Spring 容器)中。依赖注入是实现控制反转的一种方式。
3. 构造函数注入
示例
public class UserService {private final UserRepository userRepository;// 构造函数注入public UserService(UserRepository userRepository) {this.userRepository = userRepository;}// 其他业务方法public void saveUser(User user) {userRepository.save(user);}// 可选的设值注入方法public void setUserRepository(UserRepository userRepository) {this.userRepository = userRepository;}
}
3.1 构造函数注入的优点包括:
- 明确性:通过构造函数明确地声明对象需要哪些依赖,提高了代码的可读性和可理解性。、
- 不可变性:一旦对象被创建并注入了依赖,其依赖关系通常是不可变的,避免了在对象生命周期中的不必要变动。
- 线程安全:对象在创建时一次性注入所有依赖,避免了多线程环境下可能导致的并发问题。
4. 设值注入
设值注入(Setter Injection)是依赖注入(DI)的一种形式,它通过对象的公共设值方法(setter 方法)来注入依赖。在使用设值注入时,依赖关系不是通过对象的构造函数传入,而是在对象创建后,通过调用特定的设值方法来设置依赖。
public class UserService {private UserRepository userRepository;// 设值注入方法public void setUserRepository(UserRepository userRepository) {this.userRepository = userRepository;}// 其他业务方法public void saveUser(User user) {userRepository.save(user);}
}
4.1 设值注入的特点和优点
- 灵活性:与构造函数注入相比,设值注入允许在对象创建后动态地改变对象的依赖关系,因为依赖的设置是通过公共的 setter 方法完成的。
- 可选性:设值注入可以让某些依赖是可选的,即可以为某个依赖设置一个默认值,如果外部未提供,则使用默认值。
- 易于理解和维护:设值注入可以使对象的依赖关系更加明确,每个依赖可以有对应的设值方法,提高了代码的可读性和可维护性。
4.2 使用设值注入的注意事项
- 可选性和默认值:设值注入可以为某些依赖设置默认值,当外部未提供依赖时可以使用这些默认值。
- 顺序依赖:如果对象的某些依赖需要按顺序设置,设值注入可以更灵活地处理这种情况,因为可以根据具体的业务需求在合适的时间设置依赖。
- 不变性:设值注入使得对象在创建后可以动态地改变依赖关系,这在某些场景下可能带来不必要的复杂性和隐患,因此需要谨慎设计和使用。
5. 接口注入
接口注入(Interface Injection)是依赖注入(DI)的一种形式,它通过实现特定接口来实现依赖的注入。相较于常见的构造函数注入和设值注入,接口注入在现代框架和实践中较少使用,因为它具有一些局限性和复杂性,不如其他方式那样直观和灵活。
public interface DependencyInjector {void injectDependency(Object dependentObject);
}
public class MyDependencyInjector implements DependencyInjector {private MyDependency dependency;// 实现接口的注入方法@Overridepublic void injectDependency(Object dependentObject) {if (dependentObject instanceof DependentClass) {((DependentClass) dependentObject).setDependency(dependency);}}// 设置依赖对象的方法public void setDependency(MyDependency dependency) {this.dependency = dependency;}
}
5.1 接口注入的基本概念:
- 依赖注入方式:接口注入是通过实现特定接口的方式来注入依赖,该接口通常定义一个方法用于接收依赖对象。
- 接口定义:依赖注入的接口通常包括一个或多个方法,用于在对象创建后将依赖对象传递给实现类。
- 实现类:实现该接口的类需要提供方法的具体实现,以便接收依赖对象,并在适当的时机将其注入到对象中。
5.2 使用接口注入的注意事项:
- 复杂性和局限性:相较于构造函数注入和设值注入,接口注入需要定义接口和实现类,并且实现类需要显式地调用注入方法。这增加了代码的复杂性和依赖的耦合度。
- 可测试性:接口注入可能会影响代码的可测试性,因为依赖关系的设置通常需要在对象创建后进行,这可能会导致测试代码的编写和管理变得更加复杂。
- 现代框架实践:现代的依赖注入框架(如 Spring、Guice 等)通常更倾向于使用构造函数注入和设值注入,因为它们提供了更好的灵活性和清晰度。