先用一句最简单(粗暴)解释 开篇:
- 上层模块不应该依赖于底层模块,它们都应该依赖于抽象
- 抽象不应该依赖于细节,细节应该依赖于抽象
首先,让我们持有以下几个问题:
什么是依赖倒置?
什么是控制反转?
什么是依赖注入?
说实话,刚看到这几个词的时候,不知道到底是啥意思,直到多翻了几篇之后,才恍然大悟,哦,原来我经常在用啊。于是记录一下我的理解。
本篇可以帮你理清这几个关键词的意义,重新梳理自己的思想。
什么是依赖倒置呢?
- 上层模块不应该依赖于底层模块,它们都应该依赖于抽象
初学者看到这句解释,估计都想骂人了。而我们大多数同学往往总是解释时带着这句话,的确言简意赅。
我个人的理解:依赖倒置其实就相当于是 面向接口开发的 一种体现。
听起来很高大上,我直接用两段代码一探究竟吧:
public class Test { public static void main(String[] args) { Teacher teacher=new Teacher(); teacher.initWang(); } } /* 体育老师 */ class Teacher{ private Wang wang; public void initWang(){ wang=new Wang(); wang.play(); } } /* 喜欢的运动 */ interface IMotion { void play(); } /* 学生 */ class Wang implements IMotion{ @Override public void play() { System.out.println("wang 喜欢 踢足球"); } }
在上面的代码中,我们有一个测试类 Test,和一个体育老师类 Teacher , IMotion的运动接口,和一个学生Wang
运行我们的Test测试例子,会打印 ”wang 喜欢 踢足球“;
如果我们现在想再加入一个学生 Li,这个时候就需要更改 Teacher类了,比如再增加一个方法 initLi。
如下:
/* 体育老师 */ class Teacher{ private Wang wang; private Li li; public void initWang(){ wang=new Wang(); wang.play(); } public void initLi(){ li=new Li(); li.play(); } } /* 学生 */ class Li implements IMotion{ @Override public void play() { System.out.println("li 喜欢 打篮球"); } }
我知道有些同学现在可能想骂了,这个博主是傻子吗,直接不会用接口对象吗。别急,我们慢慢过渡。
依赖倒置版本
public class Test { public static void main(String[] args) { Teacher teacher = new Teacher(); teacher.play(); } } /* 体育老师 */ class Teacher { private IMotion iMotionWang; private IMotion iMotionLi; public Teacher(){ iMotionLi=new Li(); iMotionWang=new Wang(); } public void play() { iMotionWang.play(); iMotionLi.play(); } }
有些同学可能就要喊了?这不就我经常写的吗,这就叫 依赖倒置?
没错,你没有理解错,虽然这个demo现在还存一些问题(比如谁没事new两个接口),但是它已经 具备了依赖倒置的思想。
在前面的版本,我们一直new 的是具体的实例,针对具体的学生去操作,而现在我们应对的是它的抽象类,我们的的思想其实已经改变了。
那有些同学可能会问了,那这句话抽象不应该依赖于细节,细节应该依赖于抽象你怎么解释?
仔细思考一下,我们刚才写的这个接口,是不是就是这个理念,Teacher 老师类需要的是 学生具体喜欢的运动这个行为,在接口的形式下,我们无论增加多少学生,都不会影响老师这个类(不要注意多个new)。
接下来我们再来看看第二个特性,控制反转:
我们再更改一下刚才的代码,如下:
public class Test { public static void main(String[] args) { Teacher teacher = new Teacher(new Wang()); teacher.play(); } } /* 体育老师 */ class Teacher { private IMotion iMotion; public Teacher(IMotion iMotion) { this.iMotion = iMotion; } public void play() { iMotion.play(); } }
大多数同学肯定又要喊了,这不就是我经常写代码时的版本吗,这就是控制反转?你骗谁呢?
没毛病,这个真的是控制反转,再对比一下上一个版本,我们将 new 的这一步交给了具体的测试类,而不用 Teacher类来操作,这样无论以后增加多少个学生类,Teacher 都不用更改。
我们再来看看 依赖注入:
其实我们刚在已经做了依赖注入,比如我们通过构造函数将 具体的 对象 传了进去。当然也可以通过 set 方法传递;
如下:
public class Test { public static void main(String[] args) { Teacher teacher = new Teacher(); teacher.setiMotion(new Wang()); teacher.play(); } } /* 体育老师 */ class Teacher { private IMotion iMotion; public void setiMotion(IMotion iMotion) { this.iMotion = iMotion; } public void play() { iMotion.play(); } }
看完上面是不是觉得很简单,依赖倒置其实也没什么吗,没错,其实只要我们遵循设计模式,其实这些所谓的名词也没什么。上面的demo虽然看起来很简单,但是大家关注的点不应该在缺陷的demo上,而应该在代码的过渡上面,我们现在看这些操作,觉得很简单,那是因为我们已经写了太多业务代码,设计模式肯定经常涉及,所以觉得不难。
如果是新同学的话,我建议你仔细感受过渡的过程,体会面向接口开发的快乐。
当然以上这只是我个人的理解,如果有偏差,也希望大家不吝赐教。