什么是依赖倒置?直接上概念的话,非常不好理解。首先要知道什么是依赖?我们上班要坐车,吃饭需要用碗筷,我需要使用这些东西才能达成某项目的,那么我就是依赖于这些东西。反映在代码内就是A类使用B类。下面的代码Worker工人就是依赖于Tool工具。
public class Worker {
private void doWork(Tool tool){
tool.doSometing();
}
}
然后我们看一个例子,程序员小李子写代码就会需要用各种IDE。假设小李子需要写Java代码,那他就需要一个Eclipse来敲代码,小李子依赖的是Eclipse这一具体的工具,代码可以这么写。
public class XiaoLiZi {
private void coding(Eclipse eclipse){
eclipse.edit();
}
public static void main(String[] args){
//让小李子写Java代码
XiaoLiZi xiaoliZi = new XiaoLiZi();
Eclipse eclipse = new Eclipse();
xiaoliZi.coding(eclipse);
}
}
由于公司人力紧张,小李子被临时抽调到了前端项目组做前端开发了,这时候他需要一个VsCode来敲代码。由于小李子之前用的是Eclipse,我们除了调整调用处的main方法内的代码,还不得不对小李子的类进行改写。调整coding方法的入参类型为VsCode,这个时候小李子依赖的是另一个具体的工具VsCode。
public class XiaoLiZi {
private void coding(VsCode vsCode){
vsCode.edit();
}
public static void main(String[] args){
//让小李子写前端代码
XiaoLiZi xiaoliZi = new XiaoLiZi();
VsCode vsCode = new VsCode();
xiaoliz.coding(vsCode);
}
}
在金三银四的日子里,小李子的同事跳槽了,这个时候小李子不得不再兼顾回java的开发,现在小李子需要同时编写java代码和前端代码了。现在可就不再是简单调整coding方法的入参类型就能解决的了,XiaoliZi类现有的结构显然不满足让小李同时敲java代码和前端代码的功能,于是我们需要对XiaoLiZi类进行大改,把coding方法拆分成codeJava和codeHtml两个方法,分别传入Eclipse和VsCode。
public class XiaoLiZi {
private void codeHtml(VsCode vsCode){
vsCode.edit();
}
private void codeJava(Eclipse eclipse){
eclipse.edit();
}
public static void main(String[] args){
XiaoLiZi xiaoliZi = new XiaoLiZi();
//让小李子写前端代码
VsCode vsCode = new VsCode();
xiaoliZi.codeHtml(vsCode);
//让小李子写java代码
Eclipse eclipse = new Eclipse();
xiaoliZi.codeHtml(eclipse);
}
}
每次变更,我们都需要调整XiaoliZi类的代码,使用了XiaoLiZi类的代码也需要调整。一个小小的改动,影响的范围可能扩散到整个模块,有很大的影响扩散风险,原因也很简单,因为XiaoLiZi这个类和具体的IDE类Eclipse和VsCode是高度耦合的。而抽象是一种很好的解耦方法。什么是抽象?我的理解抽象其实就是概括了某一些事物总体特质的一个范围。汽车是宝马奔驰的抽象,IDE是Eclipse,VsCode的抽象。
现在我们使用抽象的思想来重构代码,满足上面的场景。我们先定义IDE的抽象接口,不管是Eclispe和VsCode都实现了IDE接口,因为他们都有edit功能。
public interface IDE {
void edit();
}
public class Eclipse implements IDE{
@Override
public void edit() {
System.out.println("Eclispe 写Java");
}
}
public class VsCode implements IDE{
@Override
public void edit() {
System.out.println("VsCode 写前端");
}
}
这个时候,如果我们想让小李子写Java代码可以这么写,注意这个时候XiaoLiZi类依赖的不再是具体的编辑器类,而是IDE这个抽象接口。
public class XiaoLiZi {
//传入的是IDE,依赖的不再是具体的编辑器类,而是IDE接口
private void coding(IDE ide){
ide.edit();
}
public static void main(String[] args){
XiaoLiZi xiaoliZi = new XiaoLiZi();
//让小李子写java代码,使用了抽象句柄IDE而不是具体类的句柄Eclipse
IDE eclipse = new Eclipse();
xiaoliZi.coding(eclipse);
}
}
当我想让小李子写前端代码时,我只需要给XiaoLiZi一个VsCode实例就行,不需要调整XiaoLiZi类的代码。
public class XiaoLiZi {
//传入的是IDE,依赖的不再是具体的编辑器类,而是IDE接口
private void coding(IDE ide){
ide.edit();
}
public static void main(String[] args){
XiaoLiZi xiaoliZi = new XiaoLiZi();
//让小李子写java代码,使用了抽象句柄IDE而不是具体类的句柄Eclipse
IDE eclipse = new Eclipse();
xiaoliZi.coding(eclipse);
//让小李子写前端代码,只需要新增两行代码,不改动原有代码
IDE vsCode = new VsCode();
xiaoliZi.coding(vsCode);
}
}
对比下来,第二种写法,对原有功能模块的影响显然是更低的,因为他不需要改动XiaoLiZi类的定义,不管我以后加多少种编辑器,都不需要改动XiaoLiZi的代码。这一改进,得益于我们把XiaoLiZi对具体编辑器的依赖关系给“颠覆”了,XiaoLiZi不依赖于具体的编辑器(Eclipse,VsCode),而是依赖于编辑器的抽象(IDE)。我们放弃了对具体实现细节(类)的依赖关系,转而去依赖抽象(接口)的思想,就是依赖倒置思想。
现在我们可以看下维基百科对“依赖倒置”的定义了:
依赖倒置的概念
我的译文:
在面向对象的程序设计中,“依赖倒置”是软件模块解耦的一种具体实现形式。这个原则颠覆了传统的上层业务模块对底层业务模块的依赖关系,致使上层代码不再依赖于底层模块代码的实现细节(而是依赖于底层的抽象)。这个原则要求以下两点:
Dependency inversion principle wikipedia
1.上层模块不能依赖于底层模块(的具体实现细节),他们都要依赖于抽象(接口)。
2.抽象不能依赖于实现细节。实现细节(具体的实现类)需要依赖于抽象(接口)。
In object-oriented design, the dependency inversion principle is a specific form of decoupling software modules. When following this principle, the conventional dependency relationships established from high-level, policy-setting modules to low-level, dependency modules are reversed, thus rendering high-level modules independent of the low-level module implementation details. The principle states: [1]
1.High-level modules should not depend on low-level modules. Both should depend on abstractions (interfaces).
2.Abstractions should not depend on details. Details (classes) should depend on abstractions.
我们在看各种设计模式的书籍,包括学习Spring AOP的过程中提起IOC的时候,经常会遇到这么一个名词“依赖倒置”。很多人包括我自己在理解这个原则的时候,经常会纠结于“倒置”这个词语,看了好几篇都不能get到这个“倒置”到底是倒置在哪。其实我个人觉得这完全就是一个翻译问题,更好的翻译我觉得应该是“依赖颠覆”。我们传统的直接的思维都是面向实现编程,而面向抽象编程就是对我们传统的面向实现编程的一种颠覆。依赖倒置,说穿了就是面向接口编程,行为类定义尽量是派生自抽象,使用时尽量用抽象的句柄。
抽象的好处
抽象的好处,对大项目而言是非常明显的。抽象是共有特质的概括范围,有了抽象,具体实现细节不会超出抽象的划定范围,不管被依赖的模块是否被完全开发完,依赖他的模块都可以并行的开发。实际上,项目的需求细节的变更也是经常的事情,使用抽象可以减少细节变更导致的代码改动量。具象的细节是多变的,但是基本上不会偏离抽象划定的范围,这样一对比,抽象明显更稳定,更适合作为依赖,就像盖房子一样,根基要稳,抽象就是根基。
参考文章:
https://www.cnblogs.com/yulang314/p/3551586.html
https://blog.youkuaiyun.com/briblue/article/details/75093382