写代码太菜了,被女朋友我分手第一天,来学习 依赖反转原则,如果你还有女朋友,那你呀好好珍惜,如果你没有,哈哈哈哈哈,那一定是代码写的太烂了!
1.什么是依赖反转原则?
1. 定义
- 高层模块不应该依赖于低层模块。两者都应该依赖于抽象。
- 抽象不应该依赖于细节。细节应该依赖于抽象。
上面这句话有点抽象,首先通过例子,先有点印象,再去进一步解读什么是依赖反转。 假设有一个报告生成的场景,我们有两个类:Report
用于生成报告,和 Printer
用于将报告打印出来。一开始的设计可能如下:
js
class Report {
public String generate() {
return "This is a report.";
}
}
class ReportClient {
private Report report;
public ReportClient() {
this.report = new Report(); //组合
}
public void generateAndPrintReport() {
String content = report.generate();
System.out.println(content);
}
}
在这个设计中,ReportClient
高层模块直接依赖于 Report
低层模块。如果需要修改或添加不同类型的报告,就需要修改 ReportClient
,违反了开闭原则。
修改之后:
java
// 面向接口编程
interface IReport {
String generate();
}
// 具体实现
class Report implements IReport {
public String generate() {
return "This is a report.";
}
}
// 依赖反转原则
class ReportClient {
private IReport report;
public ReportClient(IReport report) {
this.report = report;
}
public void generateAndPrintReport() {
String content = report.generate();
System.out.println(content);
}
}
现在,ReportClient
依赖于抽象接口 IReport
,而不是具体的 Report
类。这样,我们可以轻松地引入新的报告类型,而不影响 ReportClient
的代码。
现在再来理解依赖反转原则要求:
- 高层模块: 这是应用的主要逻辑,它应该依赖于抽象,而不是具体的实现细节(接口,抽象类)。
- 抽象: 这是一个接口或者抽象类,它定义了高层模块所需的行为(不变的,固定的)。
- 低层模块: 这是实际执行行为的类,它应该依赖于相同的抽象(多变的,可拔插)。
其核心思想是:要面向接口编程(OOD),不要面向实现编程
。看似在讲依赖反转,实际在讲面向接口编程,哈哈哈哈哈。
上面的例子用到了抽象也用到了多态,前面学习的抽象和多态的关系都忘了,在这里复习下:
- 焦点不同:
抽象
关注的是对象的本质特征和行为的抽取,而多态
关注的是同一操作在不同对象上的不同行为。 - 实现方式不同:
抽象
通过抽象类和接口
来定义规范,而多态通
过继承和接口
实现,使得同一操作可以作用在不同的对象上,产生不同的结果。 - 应用层次不同:
抽象
是在设计层次
上进行的,用于建模和设计系统的结构;而多态
则是在实现和运行
阶段体现的,用于提高代码的灵活性和可维护性。
如果只看依赖反转,拿到这里就行了,如果想了解控制反转、依赖反转、依赖注入,那可以看下下面。
2.拓展:控制反转、依赖反转、依赖注入的关系
1. 控制反转(Inversion of Control,IoC):
- 概念: IoC是一种软件设计思想,它反转了传统的程序设计中控制流的方向。在传统的程序设计中,程序员编写主体部分并控制其中的逻辑流程,而在IoC中,控制流程由框架或容器负责,程序员只需提供相应的扩展点或插件。
- 实现: IoC可以通过使用设计模式(例如模板模式、策略模式)、依赖注入等方式来实现。Spring框架是一个典型的IoC容器,它通过反射和配置文件等机制实现了IoC。
以下是一个简单的示例,假设有一个简单的Java类 UserService
,它负责用户的业务逻辑:
js
public class UserService {
private UserRepository userRepository;
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
public void createUser(String username, String email) {
// 业务逻辑,例如创建用户并保存到数据库
User user = new User(username, email);
userRepository.save(user);
}
}
在传统的方式中,你需要手动创建 UserService
类的实例,并注入它所依赖的 UserRepository
:
js
public class MyApp {
public static void main(String[] args) {
UserRepository userRepository = new UserRepositoryImpl();
UserService userService = new UserService(userRepository);
// 使用 userService 进行业务操作
userService.createUser("john_doe", "john@example.com");
}
}
而在IoC框架中,例如Spring框架,你只需配置好依赖关系,框架负责创建和管理对象,如下所示:
js
public class MyApp {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
UserService userService = context.getBean(UserService.class);
// 使用 userService 进行业务操作
userService.createUser("john_doe", "john@example.com");
}
}
2. 依赖反转原则(Dependency Inversion Principle,DIP):
- 概念: DIP是SOLID原则中的一项,它强调高层模块不应该依赖于低层模块,两者都应该依赖于抽象。这是为了减小系统中模块之间的耦合度。
- 实现: DIP可以通过使用接口、抽象类等方式实现,目的是将高层模块和低层模块的依赖关系反转,使得高层模块依赖于抽象,而不是具体实现。依赖注入是一种常见的实现方式。
3. 依赖注入(Dependency Injection,DI):
- 概念: DI是一种实现依赖反转的具体技术,它通过将依赖关系从高层模块注入到低层模块中,以达到减小模块之间耦合度的目的。通常有三种形式的依赖注入:构造函数注入、属性注入和方法注入。
- 实现: DI可以手动实现,也可以通过使用框架(如Spring)来自动进行依赖注入。框架负责在程序运行时将依赖关系注入到相应的类中。
- 不通过 new() 的方式在类内部创建依赖类对象,而是将依赖的类对象在外部创建好之后,通过构造函数、函数参数等方式传递(或注入)给类使用。
举个例子: 假设有一个 UserService
类,它依赖于一个 UserRepository
接口来执行用户相关的数据库操作:
js
public class UserService {
private UserRepository userRepository;
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
public void createUser(String username, String email) {
// 业务逻辑,例如创建用户并保存到数据库
User user = new User(username, email);
userRepository.save(user);
}
}
在这个例子中,UserService
通过构造函数接收一个 UserRepository
的实例。这就是依赖注入的一种形式。
1. 构造函数注入:
js
public class MyApp {
public static void main(String[] args) {
UserRepository userRepository = new UserRepositoryImpl(); // 具体实现
UserService userService = new UserService(userRepository);
// 使用 userService 进行业务操作
userService.createUser("john_doe", "john@example.com");
}
}
在这个例子中,通过构造函数将 UserRepository
注入到 UserService
中。
2. Setter 方法注入:
js
public class MyApp {
public static void main(String[] args) {
UserRepository userRepository = new UserRepositoryImpl(); // 具体实现
UserService userService = new UserService();
userService.setUserRepository(userRepository); // 使用 setter 方法进行注入
// 使用 userService 进行业务操作
userService.createUser("john_doe", "john@example.com");
}
}
3. 接口注入:
js
public class MyApp {
public static void main(String[] args) {
UserRepository userRepository = new UserRepositoryImpl(); // 具体实现
UserService userService = new UserService();
userService.injectUserRepository(userRepository); // 使用接口注入
// 使用 userService 进行业务操作
userService.createUser("john_doe", "john@example.com");
}
}
这些例子展示了依赖注入的不同形式,它们都达到了将依赖关系从 UserService
中解耦的目的。依赖注入使得代码更加灵活、可测试和可维护,因为依赖关系不再硬编码在类内部,而是由外部提供。
关系:
- IoC是一种
思想或概念
,它强调控制流的反转,让框架或容器负责程序的控制流程。 - DIP是一项
原则
,它强调高层模块和低层模块之间的依赖关系应该通过抽象来进行,减小耦合。 - DI是一种实现IoC和DIP的
具体技术
,它通过将依赖关系注入到类中,实现了控制反转和依赖反转。