定义:
⑴高层模块不应该依赖低层模块,二者都应该依赖其抽象
⑵抽象不应该依赖细节,细节应该依赖抽象
⑶针对接口编程,不要针对实现编程
我们通过抽象包括使用接口或者抽象类可以使各个类或模块的实现彼此独立互不影响,从而实现模块间的松耦合。
使用依赖倒置原则需要注意:
每个类尽量实现或继承自接口或抽象类,尽量避免从具体的类派生,尽量不要覆盖其基类的方法
优点:
⑴可以减少类间的耦合性
⑵提高系统稳定性
⑶提高代码可读性和可维护性
⑷降低修改程序所造成的风险
以下通过案例进行理解依赖倒置原则
版本v1
创建一个Geely用户类
写一个测试类test
输出
需求:假设这个时候Geely还要学习Python课程
那么对于Geely来说这个实体的方法是在不断补充的
这样的做法就是面向实现编程,因为整个Geely就是一个实现类,这个实现类是要经常修改的,扩展性比较差。
test类是应用层的属于高层模块,Geely类属于低层模块,而在这里test里面的实现依赖于Geely的具体实现,也就是高层模块依赖于低层模块,违背了依赖倒置的原则。
所以我们现在引入抽象解决这个问题
首先我们创建一个ICourse接口(课程) 写个studyCourse()学习方法,具体学什么可能呢,我们交给高层模块的应用层来选择
版本v2
然后写一个JavaCourse类实现ICourse接口,重写studyCourse学习课程方法
然后再写一个FECourse类实现ICourse接口,重写studyCourse学习课程方法
再往下Python也是一样的
在Geely类中三个具体的实现都可以不要了
写一个studyImoocCourse方法参数是ICourse接口,因为具体是哪个课程我们需要test应用层决定
这里呢将test类中改为如下
输出结果是一样的,但是写法是不一样的。
类图如下:
看图解释,如果我们需要学习其他课程 只需要方法哦 FECourse和JavaCourse那一栏即可,Geely这个类是不需要动的,也就是说我们是面向接口编程,我们写的扩展类都是面向ICourse接口的,而不是面向具体的Geely实现类,而对于高层模块,我们需要学习什么课程是交给test这个应用层模块来选择,这样就做到了Geely和test他们之间是解耦的,同时Geely和具体的课程实现是解耦的,但是Geely和ICourse接口是有耦合的。
那所谓的高内聚低耦合也就是尽量减少耦合,而因为有依赖关系,例如说ICourse是Geely这个类中一个方法的参数,所以他俩之间是有关系的。
所以接下来的扩展就比较简单了
比如说学习Pyhon,我们就创建PyhonCourse类实现ICourse接口,重写studyCourse学习课程方法
而对于高层模块这个test这个类,我想学习什么,通过Geely.studyImoocCourse方法,里面的参数注入进去就ok了
这样的话我们想学习新的课程,只需要新建该课程类实现课程接口。这就面向接口编程
而具体想学习什么课程交给应用层的test类这样的应用层模块。
以上这个就是通过接口方法的方式来注入具体的实现,那当然呢我们也可以通过构造器的方式来注入Geely里面的实现。
如下:在Geely中写一个构造器
版本v3
这样test中改一下就可以了
对于上面这种构造器方式其实使用起来并不方便。
如果Geely还想学别的课程 ,我们还得再new Geely()传递课程类。
因为这种注入方式只有在构造的时候再能注入进去,在Spring里面,默认是单例模式
解决方式:在Geely里面开放对ICourse的注入,用sette注入的方式
版本v4
在test中更改入下
这样就是说Geely在具体执行学习的时候,学习课程的方法不需要关心,就比如在v1版本中,Geely要学习java课程需要调用studyjavaCourse方法,而在这里学习哪个课程都是调用的同一个studyImoocCourse学习方法。
类图如下:
可以看到三个具体的实现实现ICourse,我们想学习什么课只需要去实现ICourse也就是扩展
而不是去更改实现类。
依赖倒置原则核心思想就是面向接口编程