什么是依赖倒置原则
依赖倒置原则的原始定义为包含三个方面:
- 高层模块不应该依赖底层模块,两者都应该依赖其抽象
- 抽象不应该依赖细节
- 细节应该依赖抽象
高层模块和底层模块可能好理解些,因为每一个逻辑的实现都是由原子逻辑组成的,不可分割的原子逻辑就是低层模块,原子逻辑的再组装就是高层模块。那什么是抽象,什么是细节呢?我们不妨回到 Java 语言本身去找答案吧:在 Java 中,抽象指接口或抽象类,两者均不能被实例化;细节就是实现类,实现类继承抽象类或实现接口,特点在于可被实例化,所以依赖倒置原则在 Java 中的体现为:
- 模块间的依赖通过抽象发生,实现类之间不发生直接的依赖关系,其依赖是通过接口或实现类发生的
- 接口或抽象类不依赖于实现类
- 实现类依赖于接口或抽象类
为什么需要依赖倒置原则
在说明之前我先举个学生打LOL的例子吧:
public class Student {
public void playGame(LoL lol){
System.out.println("打游戏");
}
}
public class LoL {
public void play() {
System.out.println("我爱LOL");
}
}
很普通的逻辑对吧?但大家都知道,有些学生喜欢打LOL,有些学生喜欢打DOTA啊,那怎么办?许多人第一反应就是:添加一个类呗
public class DOTA {
public void play() {
System.out.println("我爱DOTA");
}
}
但添加以后问题就来了,Student 要怎么玩DOTA?我们只能乖乖地在 Student 类里添加方法,才能让 Student 玩DOTA:
public class Student {
public void playGame(LoL lol){
System.out.println("打游戏");
}
public void playGame(DOTA dota){
System.out.println("打游戏");
}
}
观察力敏锐的人现在可能已经察觉了,我们每一次为了实际逻辑对类进行修改时,按照这种代码架构思想来修改的话,必然会做大量的无用功,随着逻辑变得复杂,代码也将不可避免地变得越来越难读。除此以外,代码还会因逻辑的添加和修改不断地修改,那么问题来了,修改带来的 Bug 谁来处理?发生大问题的时候锅给谁?
除此以外,现在进行软件开发一般都是团队协作开发,也就是说,对某个大功能块进行开发时,会划分为多个小功能模块进行开发,如果我们按照刚刚那样进行开发,把这样的代码提供给队友用,你觉得沟通效率会高吗?
这是以上的问题,导致了依赖倒置原则的诞生,依赖倒置原则就是用来减少类间耦合,提高系统的稳定性,可维护性,代码可读性的。我们不妨看看用依赖倒置原则重构的代码的例子:
首先引入抽象类 Game:
public abstract class Game {
public abstract void play();
}
LOL 和 DOTA 类都不改变,只是让它们继承于 Game 类:
public class LoL extends Game{
public void play() {
System.out.println("我爱LOL");
}
}
public class DOTA extends Game{
public void play() {
System.out.println("我爱DOTA");
}
}
修改 Student 类,让它依赖于抽象:
public class Student {
public void playGame(Game game){
game.play();
}
}
经过重构之后,我们无论添加多少个游戏,都不需要修改 Student 类,除非 Game 的抽象逻辑发生了改变。