一、定义
高层模块不应该依赖于底层模块,二者都应该依赖其抽象,抽象不应该依赖细节,细节应该依赖抽象。
- 抽象就是接口和抽象类;细节就是具体的实现类
- 依赖倒置本质:通过抽象即接口或者抽象类,使各个类和模块间彼此独立,实现模块间的松耦合
友情提醒:xmind导出的图片有点模糊,请放大查看
二、 问题的由来
2.1 问题
类A直接依赖类B,假如要将类A改为依赖类C,那么必须修改类A来完成。这种场景下,类A一般是高层模块,负责复杂的业务逻辑;类B和类C是底层模块,负责基本的原子操作;修改类A有可能给程序带来不必要的风险。
2.2 解决方案
将类A修改为依赖接口I,让类B和类C各自实现接口I,类A通过接口I间接与类B和类C发生关系,这样就会大大降低类A修改的几率。
三、 优点
- 减少类之间的耦合
- 提高系统的稳定性
- 降低并发风险
- 提高代码的可读性
四、 依赖倒置注入的实现
五、 注意
抽象类中已经实现的方法,子类最好不要覆盖
六、 使用场景
- 依赖倒置在小项目中很难体现出来,是否遵循依赖倒置原则影响不大
- 项目越大,需求改变越多,采用依赖倒置原则设计的接口或者抽象类约束实现类,维护成本会大大减少
七、 案例
妈妈讲故事,用代码描述出来。
7.1 实现需求
母亲类:
public class Mother { private static final String TAG = "Mother"; public Book book; public Mother(Book book) { this.book = book; } public void tellStory(){ Log.e(TAG,"妈妈讲故事:"+book.getContent()); } }
Book类:
public class Book { public String getContent() { return "白雪公主和七个小矮人的故事"; } }
客户端调用:
Book book = new Book(); Mother mother = new Mother(book); mother.tellStory();
执行结果:
11-15 17:07:24.718 17891-17891/com.designpatterndisclipline E/Mother: 妈妈讲故事:白雪公主和七个小矮人的故事
7.2 增加需求
母亲不光能够读书,还能够读报纸
添加一个报纸类
public class NewsPaper { public String getContent() { return "今年的双十一又突破了几千亿了..."; } }
修改母亲类中的逻辑,让他能够读报纸
/** * Created by rytong on 2017/11/15. */ public class Mother { private static final String TAG = "Mother"; private Book book; private NewsPaper newsPaper; public void setBook(Book book) { this.book = book; } public void setNewsPaper(NewsPaper newsPaper) { this.newsPaper = newsPaper; } public void ReadBook(){ Log.e(TAG,"妈妈讲故事:"+book.getContent()); } public void ReadNewsPaper(){ Log.e(TAG,"妈妈读报纸:"+newsPaper.getContent()); } }
Mother mother = new Mother(); mother.setBook(new Book()); mother.setNewsPaper(new NewsPaper()); mother.ReadBook(); mother.ReadNewsPaper();
执行结果:
11-15 17:16:00.568 23141-23141/? E/Mother: 妈妈讲故事:白雪公主和七个小矮人的故事 11-15 17:16:00.568 23141-23141/? E/Mother: 妈妈读报纸:今年的双十一又突破了几千亿了...
在刚才的代码中,我们添加了新的新闻类,修改了Mother类,修改了客户端调用的地方。其中Mother类的修改有可能会影响之前读书的逻辑,是有隐患的。
7.3 遵循依赖倒置原则重构
让书、报纸等读物实现同一个接口
/** * Created by rytong on 2017/11/15. */ public interface IBook { public String getContent(); }
报纸和故事书都实现该接口
public class Book implements IBook{ public String getContent() { return "白雪公主和七个小矮人的故事"; } } public class NewsPaper implements IBook{ public String getContent() { return "今年的双十一又突破了几千亿了..."; } }
修改母亲类中的逻辑
public class Mother { private static final String TAG = "Mother"; private IBook iBook; public void setiBook(IBook iBook) { this.iBook = iBook; } public void read(){ if (iBook instanceof Book){ Log.e(TAG,"妈妈讲故事:"+iBook.getContent()); } if (iBook instanceof NewsPaper){ Log.e(TAG,"妈妈读报纸:"+iBook.getContent()); } } }
客户端调用:
Mother mother = new Mother(); IBook iBook = new Book(); mother.setiBook(iBook); mother.read(); iBook = new NewsPaper(); mother.setiBook(iBook); mother.read();
执行结果:
11-15 17:32:17.890 32436-32436/com.designpatterndisclipline E/Mother: 妈妈讲故事:白雪公主和七个小矮人的故事 11-15 17:32:17.891 32436-32436/com.designpatterndisclipline E/Mother: 妈妈读报纸:今年的双十一又突破了几千亿了...
这里这个简单的例子,简单的模仿了新需求增加时的一些逻辑处理,本例子也并没有完全遵循依赖倒置原则,在Mother中每次修改还需要加小部分逻辑,不过比之前已经好了很多了。需要注意一点:如果方法在抽象类中已经实现,子类尽量不要覆盖该方法。