定义:
- 高层模块不应该依赖低层模块,两者都应该依赖其抽象;
- 抽象不应该依赖细节;
- 细节应该依赖抽象。
依赖倒置原则在Java语言中的表现就是:
- 模块间的依赖通过抽象发生,实现类之间不发生直接的依赖关系,其依赖关系是通过接口或抽象类产生的;
- 接口或抽象类不依赖于实现类;
- 实现类依赖接口或抽象类。
更加精简的定义就是“面向接口编程”——OOD(Object-Oriented Design,面向对象设计)的精髓之一。
采用依赖倒置原则可以减少类间的耦合性,提高系统的稳定性,降低并行开发引起的风险,提高代码的可读性和可维护性。
注意:设计是否具备稳定性,只要适当地“松松土”,观察“设计的蓝图”是否还可以茁壮地成长就可以得出结论,稳定性较高的设计,在周围环境频繁变化的时候,依然可以做到“我自岿然不动”。
司机接口(接口只是一个抽象化的概念,是对一类事物的最抽象描述)
public interface IDriver {
// 是司机就应该会驾驶汽车
public void drive(ICar car);
}
司机类的实现
public class Driver implements IDriver {
// 司机的主要职责就是驾驶汽车
public void drive(ICar car) {
car.run();
}
}
汽车接口
public interface ICar {
// 是汽车就应该能跑
public void run();
}
奔驰汽车类(宝马汽车类似,故省略)
public class Benz implements ICar {
// 汽车肯定会跑
public void run() {
System.out.println("奔驰汽车开始运行...");
}
}
业务场景
public class Client {
public static void main(String[] args) {
IDriver zhangSan = new Driver();
ICar benz = new Benz();
// 张三开奔驰车
zhangSan.drive(benz);
}
}
注意:在Java中,只要定义变量就必然要有类型,一个变量可以有两种类型:表面类型和实际类型,表面类型是在定义的时候赋予的类型,实际类型是对象的类型,如zhangSan表面类型是IDriver,实际类型是Driver。
TDD(Test-Driven Development,测试驱动开发)就是依赖倒置原则的最高级应用。JMock工具,其最基本的功能是根据抽象虚拟一个对象进行测试。
测试类
public class DriverTest extends TestCase {
Mockery context = new Junit4Mockery();
@Test
public void testDriver() {
// 根据接口虚拟一个对象
final ICar car = context.mock(ICar.class);
IDriver driver = new Driver();
// 内部类
context.checking(new Exception(){{
oneOf (car).run();
}});
driver.drive(car);
}
}
注意粗体部分,我们只需要一个ICar的接口,就可以对Driver类进行单元测试。从这一点来看,两个相互依赖的对象可以分别进行开发,孤立地进行单元测试,进而保证并行开发的效率和质量,TDD开发的精髓不就在这里吗?测试驱动开发,先写好单元测试类,然后再写实现类,这对提高代码的质量有非常大的帮助,特别适合研发类项目或在项目成员整体水平比较低的情况下采用。
对象的依赖关系有三种方式来传递:
- 构造函数传递依赖对象
- Setter方法传递依赖对象
- 接口声明依赖对象
最佳实践:
依赖倒置原则的本质就是通过抽象(接口或抽象类)使各个类或模块的实现彼此独立,不互相影响,实现模块间的松耦合。
- 每个类尽量都有接口或抽象类,或者抽象类和接口两者都具备;
- 变量的表面类型尽量是接口或者是抽象类(比如工具类就一般不需要接口或者抽象类)。
- 任何类都不应该从具体类派生
- 尽量不要覆写基类的方法
- 结合里氏替换原则使用
通俗的规则:接口负责定义public属性和方法,并且声明与其他对象的依赖关系,抽象类负责公共构造部分的实现,实现类准确的实现业务逻辑,同时在适当地时候对父类进行细化。