1, 定义
按照惯例, 首先我们来看一下依赖倒置原则的定义.
抽象不应该依赖于细节, 细节应当依赖于抽象. 换言之, 要针对接口编程, 而不是针对实现编程.
为什么要这样说呢?
因为细节具有易变性, 非常的不稳定. 很多时候, 需求改变就会给细节带来改变.
而抽象则是相对稳定的, 抽象是从众多的事物中抽取出共同的, 本质性的特征, 是比较难被改变的.
所以, 我们肯定要选择对抽象编程, 而不选择对细节编程.
抽象在 Java 中则指的是抽象类或者接口; 细节则是代表着具体实现.
对接口编程, 是写出健壮性代码的根本, 是优秀程序员必备的基本素质.
2, 含义
1, 高层模块不应该依赖低层模块, 两者都应该依赖其抽象
无论是高层模块, 还是低层模块, 全部都属于具体的实现, 属于细节, 而细节肯定是不能相互依赖的.
他们都应该依赖于抽象.
2, 抽象不应该依赖细节
都不应该依赖于细节, 特别是抽象更加不应该依赖细节.
3, 细节应该依赖抽象
都应该依赖抽象, 细节依赖抽象, 抽象也依赖抽象.
3, 代码
1, 接口方法中声明依赖对象
- package com.fanqiekt.principle.inversion;
- /**
- * 厨师接口
- *
- * @author 番茄课堂 - 懒人
- */
- public interface IChef {
- /**
- * 做饭
- */
- void cooking();
- }
复制代码
厨师都会做饭.
- package com.fanqiekt.principle.inversion;
- /**
- * 四川厨师
- *
- * @author 番茄课堂 - 懒人
- */
- public class SiChuanChef implements IChef {
- @Override
- public void cooking() { System.out.println("四川厨师做饭, 多放辣椒.");
- }
- }
复制代码
厨师的实现类, 川菜厨师, 其中的逻辑都属于细节, 例如川菜厨师有自己做饭的套路 (不能没有辣).
- package com.fanqiekt.principle.inversion;
- /**
- * 山东厨师
- *
- * @author 番茄课堂 - 懒人
- */
- public class ShanDongChef implements IChef {
- @Override
- public void cooking() {
- System.out.println("山东厨师做饭, 用葱姜蒜.");
- }
- }
复制代码
另一个厨师的实现类, 鲁系厨师, 鲁菜厨师也有自己做饭的套路 (善用葱姜蒜).
- package com.fanqiekt.principle.inversion;
- public interface IWaiter {
- /**
- * 点餐
- * @param chef 指定做饭的菜系厨师
- */
- void order(IChef chef);
- }
复制代码
服务员的抽象接口, 服务员都需要为客人点餐.
order(IChef chef) 方法就是接口方法中声明依赖对象.
在接口中定义该方法, 并将依赖的抽象对象作为参数传入.
- package com.fanqiekt.principle.inversion;
- /**
- * 接口方法中声明依赖对象
- *
- * @author 番茄课堂 - 懒人
- */
- public class Waiter implements IWaiter{
- @Override
- public void order(IChef chef){
- if(chef!=null) {
- chef.cooking();
- }
- }
- }
复制代码
服务员的实现类, 点餐是让传入的厨师去做饭.
- IChef sichuanChef = new SiChuanChef();
- IChef shandongChef = new ShanDongChef();
- IWaiter waiter = new Waiter();
- waiter.order(sichuanChef);
- waiter.order(shandongChef);
复制代码
将抽象对象作为 order 方法参数.
四川厨师做饭
山东厨师做饭
复制代码
运行结果.
每次调用方法时, 都传入依赖对象.
优点是灵活, 每次都可以传入不同的依赖对象.
缺点是繁琐, 每次都需要传入依赖对象.
2, 构造方法传递依赖对象
- package com.fanqiekt.principle.inversion;
- public interface IWaiter {
- /**
- * 点餐
- */
- void cooking();
- }
复制代码
服务员接口修改: cooking 方法去掉参数.
- package com.fanqiekt.principle.inversion;
- /**
- * 构造方法传递依赖对象
- *
- * @author 番茄课堂 - 懒人
- */
- public class Waiter implements IWaiter {
- private IChef chef;
- /**
- * 构造方法中传入依赖的抽象对象
- * @param chef 厨师抽象接口
- */
- public Waiter(IChef chef){
- this.chef = chef;
- }
- @Override
- public void cooking(){
- if(chef!=null) {
- chef.cooking();
- }
- }
- }
复制代码
通过构造方法传入依赖的抽象对象.
- Waiter waiter1 = new Waiter(sichuanChef);
- waiter1.cooking();
- Waiter waiter2 = new Waiter(shandongChef);
- waiter2.cooking();
复制代码
运行看一下结果.
四川厨师做饭
山东厨师做饭
复制代码
首次创建的时候就确定了依赖, 既是优点又是缺点.
优点是避免了被修改.
缺点是更换依赖, 就需要重新再创建对象了.
3,Setter 方法传递依赖对象
- package com.fanqiekt.principle.inversion;
- /**
- * Setter 方法传递依赖对象
- *
- * @author 番茄课堂 - 懒人
- */
- public class Waiter implements IWaiter {
- private IChef chef;
- public void setChef(IChef chef){
- this.chef = chef;
- }
- @Override
- public void cooking(){
- if(chef!=null) {
- chef.cooking();
- }
- }
- }
复制代码
通过 set 方法赋值依赖对象.
- Waiter plan = new Waiter();
- plan.setChef(sichuanChef);
- plan.cooking();
- plan.setChef(shandongChef);
- plan.cooking();
复制代码
运行看一下结果.
四川厨师做饭
山东厨师做饭
复制代码
Setter 既可以更换依赖对象, 也不用每次调用方法时都传入依赖对象.
3, 优点
我们来总结一下依赖倒置原则的几个优点.
降低风险 依赖抽象, 大大提高代码的健壮性, 风险自然而然就被降低了.
易维护易扩展 依赖抽象, 才会有框架, 基于框架, 扩展会更方便, 维护起来也更省事.
增加开发速度 定好抽象结构就可以并行开发了, 而不用过多的被他人的进度干预.
4, 嘻哈说
闲来无事听听曲, 知识已填脑中去; 学习复习新方式, 头戴耳机不小觑. 番茄课堂, 学习也要酷. 接下来, 请您欣赏懒人为依赖倒置原则创作的歌曲.
试听请点击这里 https://v.qq.com/x/page/a07572l2dg8.html
嘻哈说: 依赖倒置原则
需要依赖厨师为我做顿美食
不用管他到底川系或者徽系
只依赖厨师接口做法多么美丽
不然修改起来牵扯太多的话是多么费事
set 方法构造方法接口方法可以将依赖做个配置
依赖倒置原则就是这么回事
抽象不依赖细节 细节依赖抽象
高层不依赖低层 都应该依赖抽象
面向接口编程记住这点才能在代码路走的够长
易扩展易维护风险降低增加开发速度
就像番茄课堂一样酷
复制代码
来源: https://juejin.im/post/5b7427b9518825613d38974b