DIP全称
DIP, Dependence Inversion Principle , 依赖倒置原则
定义
模块间的依赖通过抽象发生,实现类之间不发生直接的依赖关系,其依赖关系是通过接口和抽象类产生的。
即面向接口编程,或者说是面向抽象编程。依赖抽象(接口或者抽象类),而不依赖具体实现。高层次(调用端)的模块不依赖于低层次(实现类)的模块的实现细节。
优点
- 降低类之间的耦合性
- 提高系统的稳定性
- 降低修改程序造成的风险
实现
-
问题由来: 类A直接依赖类B,假如要将类A改为依赖类C,则必须通过修改类A的代码来达成。这种场景下,类A一般是高层模块,负责复杂的业务逻辑;类B和类C是低层模块,负责基本的原子操作;假如修改类A,会给程序带来不必要的风险。
-
解决方案:
将类A修改为依赖接口I,类B和类C各自实现接口I,类A通过接口I间接与类B或者类C发生联系,则会大大降低修改类A的几率。
以抽象方式耦合是依赖倒转原则的关键。抽象耦合关系总要涉及具体类从抽象类继承,并且需要保证在任何引用到基类的地方都可以改换成其子类,因此,里氏代换原则是依赖倒转原则的基础。
实例
场景:母亲给孩子讲故事,只要给她一本书,她就可以照着书给孩子讲故事了。
class Book {
public String getContent() {
return "很久很久以前......";
}
}
class Monther {
public void narrate(Book book) {
System.out.println("妈妈开始讲故事");
System.out.println(book.getContent());
}
}
public class DIPClient {
public static void main(String[] args) {
Monther monther = new Monther();
monther.narrate(new Book());
}
}
输出结果:
妈妈开始讲故事
很久很久以前......
如果此时需要讲报纸上的内容,就需要再新建一个类Newspaper
class Newspaper {
public String getContent() {
return "金融风暴卷土而来......";
}
}
class Book {
public String getContent() {
return "很久很久以前......";
}
}
class Monther {
public void narrate(Book book) {
System.out.println("妈妈开始讲书上的故事");
System.out.println(book.getContent());
}
public void narrate(Newspaper newspaper) {
System.out.println("妈妈开始讲报纸上的内容");
System.out.println(newspaper.getContent());
}
}
public class DIPClient {
public static void main(String[] args) {
Monther monther = new Monther();
monther.narrate(new Book());
monther.narrate(new Newspaper());
}
}
输出结果:
妈妈开始讲书上的故事
很久很久以前......
妈妈开始讲报纸上的内容
金融风暴卷土而来......
如果再来个讲头条的内容,又要新建一个类,然后改动Monther类,这样做不太合理。此时新建接口类,使其依赖于接口,而不是具体实现类,达到Monther类不用修改的目的。
interface IReader {
String getContent();
}
class Newspaper implements IReader {
@Override
public String getContent() {
System.out.println("妈妈开始讲报纸上的内容");
return "金融风暴卷土而来......";
}
}
class Book implements IReader {
@Override
public String getContent() {
System.out.println("妈妈开始讲书上的故事");
return "很久很久以前......";
}
}
class Monther {
public void narrate(IReader reader) {
System.out.println(reader.getContent());
}
}
public class DIPClient {
public static void main(String[] args) {
Monther monther = new Monther();
monther.narrate(new Book());
monther.narrate(new Newspaper());
}
}
输出结果:
妈妈开始讲书上的故事
很久很久以前......
妈妈开始讲报纸上的内容
金融风暴卷土而来......
Mother类与接口IReader发生依赖关系,而Book和Newspaper都属于读物的范畴,他们各自都去实现IReader接口,这样就符合依赖倒置原则了。
传递依赖关系有三种方式,以上的例子中使用的方法是接口传递,另外还有两种传递方式:构造方法传递和setter方法传递。
在实际编程中,我们一般需要做到如下3点:
- 低层模块尽量都要有抽象类或接口,或者两者都有。
- 变量的声明类型尽量是抽象类或接口。
- 使用继承时遵循里氏替换原则。