依赖倒置原则-DIP
依赖倒置原则定义
依赖倒置原则(Dependence Inversion Principle, DIP)
是指设计代码结构时,高层模块不应该依赖低层模块,二者都应该依赖其抽象,抽象不应该依赖细节,细节应该依赖抽象。
官方定义: High level modules should not depend upon low level modules. Both should depend upon abstractions.Abstractions should not depend upon details. Details should depend upon abstractions.
高层模块不应该依赖低层模块,二者都应该依赖其抽象。抽象不应该依赖细节,细节应该依赖抽象。
从上面的定义解释中我们可以看出依赖倒置原则包含如下三层含义:
- 高层模块不应该依赖低层模块,两者都应该依赖其抽象
- 抽象不应该依赖细节
- 细节应该依赖抽象
由于在软件设计中,细节具有多变性,而抽象层则相对稳定,因此以抽象为基础搭建起来的架构要比以细节为基础搭建起来的架构稳定得多。这里的抽象多指接口或抽象类,细节多指实现类。使用接口或抽象类的目的是制定好规范和契约,而不去涉及任何具体的操作,把展现细节的任务交给它们的实现类去实现。通过依赖倒置,可以减少类与类之间的耦合性,提高系统和稳定性,提高代码的可读性和可维护性,并且有够降低修改程序所造成的风险。
依赖倒置原则的作用:
- 依赖倒置原则可以降低类间的耦合性
- 依赖倒置原则可以提高系统的稳定性
- 依赖倒置原则可以减少并行开发引进的风险
- 依赖倒置原则可以提高代码的可读性和可维护性
依赖倒置原则的实现方法:
依赖倒置原则是通过面向对接接口编程来降低类的耦合性,所以在实际编程中只要遵循以下4点,就能在项目中满足这个原则
- 每个类尽量提供接口可抽象类,或两者都具备
- 变量的声明类型尽量是接口或抽象类
- 任何类都不应该从具体类中派生
- 使用继承时尽量遵循里氏原则
使用依赖倒置原则解决实际问题:
我们以课程为例,首先创建一个类 Tom,Tom喜欢学习,目前他在学习Java和Python课程
public class Tom{
public void studyJavaCourse(){
System.out.println("Tom在学习Java课程");
}
public void studyPythonCourse(){
System.out.println("Tom在学习Python课程");
}
}
然后编写客户端测试代码
public static void main(String[] args){
Tom tom = new Tom();
tom.studyJavaCourse();
tom.studyPythonCourse();
}
随着学习兴趣暴涨,Tom还想学习AI课程,这个时候,就需要扩展业务,代码要从底层到高层修改代码 首先需要在Tom类中增加 studyAICourse()的方法,最后在高层也要追回调用,这样一来,在系统发布以后,实际上非常不稳定,在修改代码的同时会带来意想不到的风险。因此我们需要优化下代码,
首先创建一个课程的抽象接口ICourse
public interface ICourse{
void study();
}
然后写Java课程类
public class JavaCourse implements ICourse{
@Override
public void study(){
System.out.println("Tom在学习Java课程");
}
}
再实现Python课程类
public class PythonCourse implements ICourse{
@Override
public void study(){
System.out.println("Tom在学习Python课程");
}
}
最后Tom类
public class Tom{
public void study(ICourse course){
course.study();
}
}
我们看看客户端测试代码
public static void main(String[] args){
Tom tom = new Tom();
tom.study(new JavaCourse());
tom.study(new PythonCourse());
}
这样修改以后,无论Tom接下来想学什么,我们只需要新建一个类,通过传参的方式告知Tom,而不需要修改底层代码。实际上,这是一种大家非常熟悉的方式,叫依赖注入;
依赖注入常见的三种注入方式(接口注入,构造器注入,Setter注入)
注入的方式还有构造器注入方式和Setter注入方式,下面来看构造器注入方式
public class Tom(){
private ICourse course;
public Tom(ICourse course){
this.course = course;
}
public void study(){
course.study();
}
}
客户端测试代码
public static void main(String[] args){
Tom tom = new Tom(new JavaCourse());
tom.study();
}
根据构造器注入方式,当调用时,每次都要创建实例。那么,如果Tom是全局单例,则只能选择Setter注入,我们再修改一下
public class Tom{
private ICourse course;
public void setCourse(ICourse course){
this.course = course;
}
public void study(){
course.study();
}
}
客户端代码
public static void main(String[] args){
Tom tom = new Tom();
tom.setCourse(new JavaCourse())
tom.study();
tom.setCourse(new PythonCourse())
tom.study();
}
最终得到的类图如下