一、定义
依赖倒转原则(Dependence Inversion Principle, DIP)的定义:高层模块不应该依赖于低层模块,它们都应该依赖于抽象。抽象不应该依赖于细节,细节应该依赖于抽象。
另一种表述:要针对接口编程,不要针对实现编程。
对各种概念进行一个描述:
- 低层模块:不可分割的原子逻辑,可能会根据业务逻辑经常变化。
- 高层模块:低层模块的再组合,对低层模块的抽象。
- 抽象: 接口或抽象类(是底层模块的抽象,特点:不能直接被实例化)
- 与接口或抽象类对应的实现类:低层模块的具体实现(特点:可以直拉被实例化)
依赖倒置原则的优点:
- 可以通过抽象使各个类或模块的实现彼此独立,不互相影响,实现模块间的松耦合(也是本质)
- 可以规避一些非技术因素引起的问题(项目大时,需求变化的概率也越大,通过采用依赖倒置原则设计的接口或抽象类对实现类进行约束,可以减少需求变化引起的工作量剧增情况。同时,发生人员变动,只要文档完善,也可让维护人员轻松地扩展和维护)
- 可以促进并行开发(如,两个类之间有依赖关系,只要制定出两者之间的接口(或抽象类)就可以独立开发了,规范已经定好了,而且项目之间的单元测试也可以独立地运行,而TDD开发模式更是DIP的最高级应用(特别适合项目人员整体水平较低时使用))
(参考自设计模式六大原则例子(四)-- 依赖倒置原则(DIP)例子)
二、分析
依赖倒转原则就是指:代码要依赖于抽象的类,而不依赖于具体的类;要针对接口或抽象类编程,而不是针对具体类编程。也就是说,在程序代码中传递参数时或在组合聚合关系中,尽量引用层次高的抽象类,即使用接口和抽象类进行变量类型声明、参数类型声明、方法返回类型声明,以及数据类型的转换等,而不要用具体类来做这些事情。为了确保该原则的应用,一个具体类应当只实现接口和抽象类中声明过的方法,而不要给出多余的方法,否则将无法调用到在子类中增加的新方法。
依赖倒转原则的常用实现方式之一是在代码中使用抽象类,而将具体类放在配置文件中。也就是说要推迟对具体类的定义,尽量在代码中针对抽象编程,这样有助于设计出能够快速作出变更的解决方案,以便应对项目需求的变化。
下面介绍一下依赖倒转原则中经常提到的两个概念——类之间的耦合和依赖注入。
1.类之间的耦合
在面向对象系统中,两个类之间通常可以发生三种不同的耦合关系(依赖关系):
(1)零耦合关系:如果两个类之间没有任何耦合关系,称之为零耦合
(2)具体耦合关系:具体耦合发生在两个具体类中(可实例化的类)之间,由一个类对另一个具体类实例的直接引用产生
(3)抽象耦合关系:抽象耦合关系发生在一个具体类和一个抽象类之间,也可以发生在两个抽象类之间,使两个发生关系的类之间存在最大的灵活性。由于在抽象耦合中至少有一端是抽象的,因此可以通过不同的具体实现来进行扩展
依赖倒转原则要求客户端依赖于抽象耦合,以抽象方式耦合是依赖倒转原则的关键。由于一个抽象耦合关系总要涉及具体类从抽象类继承,并且需要保证在任何引用到基类的地方都可以替换成其子类,因此,里氏代换原则是依赖倒转原则的基础。
2.依赖注入
依赖注入就是将一个类的对象传入另一个类,注入时应该尽量注入父类对象,而在程序运行时再通过子类对象来覆盖父类对象,依赖注入有以下三种方式:
(1)构造注入(Constructor Injection)
通过构造函数注入实例变量
public interface AbstractBook {
public void view();
}
public interface AbstractReader {
public void read();
}
public class ConcreteBook implements AbstractBook {
public void view() {
......
}
}
public class ConcreteReader implements AbstractReader {
private AbstractBook book;
public ConcreteReader(AbstractBook book) {
this.book = book;
}
public void read() {
book.view();
}
}
(2)设值注入(Setter Injection)
通过Setter方法注入实例变量
public interface AbstractBook {
public void view();
}
public interface AbstractReader {
public void setBook(AbstractBook book);
public void read();
}
public class ConcreteBook implements AbstractBook {
public void view() {
......
}
}
public class ConcreteReader implements AbstractReader {
private AbstractBook book;
public void setBook(AbstractBook book) {
this.book = book;
}
public void read() {
boook.view();
}
}
(3)接口注入(Interface Injection)
通过接口方法注入实例变量
public interface AbstractBook {
public void view();
}
public interface AbstractReader {
public void read(AbstractBook book);
}
public class ConcreteBook implements AbstractBook {
public void view() {
......
}
}
public class ConcreteReader implements AbstractReader {
public void read(AbstractBook book) {
book.view();
}
}