依赖倒置原则的定义
依赖倒置原则的英文为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中的表现是:
- 模块间的依赖通过抽象发生,实现类之间不发生直接的依赖关系,其依赖关系是通过接口或抽象类产生的;
- 接口或抽象类不依赖于实现类;
- 实现类依赖接口或抽象类;
更加精简的定义就是“面向接口编程”。
依赖倒置原则的示例
先来看一个示例,小明家里有一辆大众汽车,小明每天开车去上班。类图如下:
小明有一个方法drive()可以驾驶,大众车有一个方法run()来运行,代码如下:
|
|
|
|
这样子小明就可以开他自己的大众车去上班了,有一天小明的车坏了,送到汽车厂维修,没办法,只有借他父亲的宝马车一用。
宝马车代码
小明拿到父亲的宝马车了,但是发现自己怎么样也不能开动车子,小明的驾驶方法里面只有开动大众车的方法,要让宝马车开动起来,我们还要去修改驾驶员类,这样也太不合理了。那么怎么办呢,现在我们通过依赖倒置原则,让车和驾驶员之间通过接口来依赖。
我们重新设计一下,类图如下:
司机接口
司机的实现类
这样在IDriver中,通过传入ICar接口实现了抽象之间的依赖关系,Driver实现类传入的是ICar接口,至于是哪一种车,需要在更高层的模块来声明。
汽车接口
大众汽车实现类
宝马汽车实现类
现在再来看一下上班过程的代码
修改了设计之后,汽车和驾驶员之间的强耦合关系就解开了,它们之间通过接口进行依赖,这样的话,如果我们以后要增加汽车Benz类,只需要让Benz类实现ICar接口就可以使用了,这样也不用去修改其他的类和接口,依赖倒置原则的好处就在这里体现出来了。
依赖的三种实现方式
对象的依赖关系有三种方式来传递,分别是接口传递,构造函数传递和setter方法传递
接口声明依赖对象
在上面的例子中,就采用了接口声明依赖的方式,这种方法也叫做接口注入。
构造函数传递依赖对象
在类中通过构造函数来声明依赖对象,按照依赖注入的说法,这种方法叫做构造函数注入,按照这种方式写的IDriver和Driver如下:
|
|
Setter方法传递依赖对象
在抽象中设置Setter方法声明依赖关系,这种方法也叫做Setter依赖注入,按照这种方式写的IDriver和Driver如下:
|
|
依赖倒置原则的总结
优点
- 降低了类之间的耦合性;
- 提高系统的稳定性;
- 降低并行开发的风险;
- 提高代码的可读性和可维护性;
- 减少需求变化时原有代码大量改变情况的出现;
实践
要做到依赖倒置原则,可以从以下几个方面入手:
- 每个类尽量都有接口或抽象类,或者接口和抽象类都有
- 变量的表面类型尽量是接口或者是抽象类
- 类不应该从具体类派生
- 尽量不要覆写基类的方法
- 结合里氏替换原则使用