依赖倒置原则
依赖倒置原则(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.
翻译过来,有三重含义:
高层模块不应该依赖底层模块,两者都应该依赖其抽象。
抽象不应该依赖细节。
细节应该依赖抽象。
更加精髓的定义就是"面向接口编程"——面向对象设计的精髓之一。
依赖倒置原则在Java语言中的表现就是:
模块间的依赖通过抽象发生,实现类之间不发生直接的依赖关系,其依赖关系是通过接口或抽象类产生的。
接口或抽象类不依赖于实现类。
实现类依赖与接口或抽象类。
采用依赖倒置原则可以减少类间的耦合性,提高系统的稳定性,降低并发开发引起的风险,提高代码的可读性和可维护性。
依赖是可以传递的。只要做到抽象依赖,即使是多层的依赖传递也无所畏惧。
** 为了说明依赖倒置到底干了什么,我们以一个反例来说明,如果不使用依赖倒置,会发生什么事情 **
我有一辆奔驰车,奔驰车有个方法能启动它
package 依赖倒置原则反证;
public class Benz {
public void run() {
System.out.println("奔驰启动。。。");
}
}
我还有一辆宝马车,宝马车有个方法能启动它
package 依赖倒置原则反证;
public class BMW {
public void run() {
System.out.println("宝马启动。。。");
}
}
我现在想找个司机,司机有个dirve方法可以开车,可是这个司机有个怪毛病,他在开车之前必须告诉他要开什么车,否则他不开。
package 依赖倒置原则反证;
/**
* 司机源代码,司机和奔驰车是紧耦合关系,目前司机只能开奔驰,哪怕我的宝马方法已经建好了,司机也不能开宝马
* @author xuxiao
*
*/
public class Driver {
//司机和奔驰紧耦合,司机只能开奔驰
public void dirve(Benz benz) {
benz.run();
}
}
有没有解决方案呢,肯定有啊,我们应该学过方法的重载了,只要参数不同,我们就可以写一个新的drive方法来让司机可以开两种车。
public void dirve(BMW bmw) {
bmw.run();
}
package 依赖倒置原则反证;
public class Client {
public static void main(String[] args) {
Driver zhangsan = new Driver();
Benz benz = new Benz();
zhangsan.dirve(benz);
//现在司机想开宝马车了,但是drive方法和奔驰是紧耦合的关系,无法实现。
//解决方案只有在driver类中重载drive方法,这样真的好吗?
}
}
我们来设想一个场景,要是我家就是卖车的,有几百种型号,司机每次开之前都来问我,这车啥牌子的,不说我就不开!对应到程序里就是我要把drive方法重载几百次。
那依赖倒置怎么解决这个问题?
车子抽象为接口,所有车都实现这个接口
package 依赖倒置原则;
public interface ICar {
public void run();
}
package 依赖倒置原则;
public class Benz implements ICar{
public void run() {
System.out.println("奔驰启动。。。");
}
}
package 依赖倒置原则;
public class BMW implements ICar{
public void run() {
System.out.println("宝马启动。。。");
}
}
司机也抽象为接口,是司机就会开车,不管车型号
package 依赖倒置原则;
public interface IDriver {
public void drive(ICar car);
}
package 依赖倒置原则;
/**
*
* @author xuxiao
*
*/
public class Driver implements IDriver{
@Override
//司机只管开车,开的什么车?不知道!我开的的是抽象的车,什么都行
public void drive(ICar car) {
car.run();
}
}
再来看场景类
package 依赖倒置原则;
public class Client {
public static void main(String[] args) {
//zhangsan的声明类型是IDriver,zhangsan的实际类型是Driver
IDriver zhangsan = new Driver();
Benz benz = new Benz();
BMW bmw = new BMW();
zhangsan.drive(benz);
zhangsan.drive(bmw);
}
}
最佳实践:
每个类尽量都有接口或抽象类,或者两者都具备。接口负责定义public属性和方法,并且声明与其他对象的依赖关系,抽象类负责公共构造部分的实现,实现类准确的实现业务逻辑,同时在适当的世界对父类进行细化。
变量的表面类型尽量是接口或者是抽象类。
任何类都不应该从具体类派生。
尽量不要覆写(Override)基类的方法。
结合里氏替换原则使用。
"面向接口编程"是依赖倒置原则的核心。
一个项目的终极目标,是投产上线和盈利。技术只是实现目的的工具。