定义:高层模块不应该依赖低层模块,两者都应该依赖其抽象;抽象不应该依赖细节;细节应该依赖抽象。
场景:类A直接依赖类B,假如要将类A改为依赖类C,则必须通过修改类A的代码来达成。这种场景下,类A一般是高层模块,负责复杂的业务逻辑;类B和类C是低层模块,负责基本的原子操作;假如修改类A,会给程序带来不必要的风险。
解决方案:遵循依赖倒置原则,将类A修改为依赖接口I,类B和类C各自实现接口I,类A通过接口I间接与类B或者类C发生联系,则会大大降低修改类A的几率。
上面用到很多泛泛的词语,为了帮助理解,对其进行大白话化:抽象就是指接口或抽象类,两者都是不能直接被实例化的;细节就是实现类,实现接口或继承抽象类而产生的类就是细节,其特点就是,可以直接被实例化。高层模块就是调用端,低层模块就是具体实现类。依赖倒置原则在 Java 语言中的表现就是:模块间的依赖通过抽象发生,实现类之间不发生直接的依赖关系,其依赖关系是通过接口或抽象类产生的。这又是一个将理论抽象化的实例,其实一句话就可以概括:面向接口编程,或者说是面向抽象编程,这里的抽象指的是接口或者抽象类。
根据上面的场景,用代码更直观的来理解依赖倒置原则。现在有这样一个需求,一群孩子玩球类游戏,只要给他们一个球,便可开心的玩耍。代码如下:
篮球类:
/**
* Created by LJW on 2018/8/24.
* 篮球类
*/
public class Basketball {
public String playBall(){
return "一群孩子正在开心的打篮球......";
}
}
孩子类:
/**
* Created by LJW on 2018/8/24.
* 孩子类
*/
public class Children{
public void play(Basketball basketball){ //通过参数依赖了篮球类
System.out.println(basketball.playBall());
}
}
开始游戏:
Children children = new Children();
children.play(new Basketball());
输出结果:一群孩子正在开心的打篮球......
孩子玩的很开心,假如有一天,小朋友说,你天天让我们玩篮球,够够的,能不能换成踢足球?现在需求变了,好,我们新建一个足球类:
**
* Created by LJW on 2018/8/24.
* 足球类
*/
public class Football{
public String playBall() {
return "一群孩子正在开心的踢足球......";
}
}
这时我们发现,这群孩子踢不了足球了!因为他们依赖了篮球类,孩子类和篮球类之间的耦合性太高了。要想让他们可以踢足球,必须来修改孩子类的代码,这不严重违背了开闭原则了吗?
如果类与类直接依赖于细节(如上面的孩子类和篮球类),那么他们之间就有了直接的耦合,当具体实现需要变化时,意味着要同时修改依赖者的代码,这限制了程序的可扩展性。下面我们遵循依赖倒置原则来重构上面的代码:
首先,我们引入一个球类的接口,只要属于球类运动,都实现该接口:
/**
* Created by LJW on 2018/8/24.
* 球类接口
*/
public interface IPlayBall {
String playBall();
}
修改篮球类和足球类,让其实现IPlayBall接口:
/**
* Created by LJW on 2018/8/24.
* 篮球类
*/
public class Basketball implements IPlayBall {
@Override
public String playBall() {
return "一群孩子正在开心的打篮球......";
}
}
/**
* Created by LJW on 2018/8/24.
* 足球类
*/
public class Football implements IPlayBall{
@Override
public String playBall() {
return "一群孩子正在开心的踢足球......";
}
}
最后修改孩子类,不再让其依赖任何具体的球类,而是依赖球类接口,这样就遵循了依赖倒置原则:
/**
* Created by LJW on 2018/8/24.
* 孩子类
*/
public class Children{
public void play(IPlayBall playBall){
System.out.println(playBall.playBall());
}
}
孩子的游戏开始啦:
Children children = new Children();
children.play(new Basketball());
children.play(new Football());
输出结果:
一群孩子正在开心的打篮球......
一群孩子正在开心的踢足球......
这时孩子们竖起来崇拜的大拇指~~这才是我们想要的!
这样修改后,可以随意的来扩展各种球类运动,而不再修改孩子类了。通过遵循依赖倒置原则,降低了类之间的耦合性,提高了系统的稳定性,减少了修改程序造成的风险。通过上面的例子我们可以发现,依赖倒置原则的核心是:抽象。理解了抽象,也就掌握了依赖倒置原则。