对于依赖倒置,常用Spring框架的开发人员可能非常熟悉,这是在Spring 的应用程序经常使用的一种设计原则,他有以下要求:
- 高层模块不应该依赖低层模块,两者都应该依赖其抽象 假设AImpl 不应该依赖于 BImpl,而是 A 依赖于 B
- 抽象不应该依赖细节,而是细节依赖抽象
在Java语言中,细节指的就是实现类,具体的实现细节;而抽象指的就是接口或者抽象类,这两者是不能被实例化的
在Java语言中实现上面的细节就是,面向接口开发 (OOD,Object-Orented Design),具有以下特性:
- 模块之间通过抽象相互依赖,实现类之间不发生直接的依赖
- 接口或者抽象类不依赖于实现类
- 实现类依赖于接口或者抽象类
3.1 依赖倒置的案例
采用DIP 原则主要有以下优点:
- 采用DIP 可以减少代码间的耦合,降低并行开发的风险
- 提高代码 的可维护性以及可读性
3.1.1 初步设计方案
创建汽车驾驶员 Driver类型,他可以驾驶BenZ类型的汽车,我们初步的设计可能是下面的UML设计图,
public class DIP {public static void main(String[] args) {// tom 驾驶 BenZ 汽车Driver tom = new Driver();tom.driver(new BenZ());}static class Driver {public void driver(BenZ benZ) {benZ.run(100);}}static class BenZ {public void run(int speed) {System.out.println("奔驰车正在以" + speed + " 速度运行");}}}/*奔驰车正在以100 速度运行*/
思考: 🤔 基于上面的代码,驾驶员 tom 后面有可能会驾驶BWM,QQ 等汽车,怎么办?
那么,根据需要求,我可以创建BWM 类如下:
static class BWM {public void run(int speed) {System.out.println("BWM正在以" + speed + " 速度运行");}}
但是显然 Driver中的drive方法的参数只能是BenZ类 public void driver(BenZ benZ) 只能驾驶BenZ类的汽车,不能驾驶BWM 类,这显然不合理:
- Driver类依赖于BenZ这个实现类
- 需要同时维护多个汽车类,维护成本大大提高
- 在Driver创建的时候,必须等到BenZ实现才能继续开发,这不能进行单元测试,甚至不能运行,提高了并行开发的风险
3.1.2 改进方案
根据依赖注入的要求我们可以知道,各个类之间不能依赖于细节,而应该依赖于其抽象,基于此,我们将汽车驾驶员和汽车进行抽象,上层仅仅依赖于抽象,而非细节,实现UML如下:
我们将驾驶员和汽车做个抽象:
interface ICar {/** 汽车的名称 */String name();/** 汽车运行 */void run(int speed);}interface IDriver {/** 驾驶ICar 避免依赖其实现的子类 */void drive(ICar car, int speed);}
继续实现其ICar和IDriver的实现细节
static class Driver implements IDriver {@Overridepublic void drive(ICar iCar, int speed) {iCar.run(speed);}}static class BenZ implements ICar {@Overridepublic String name() {return "BenZ";}@Overridepublic void run(int speed) {System.out.println("奔驰车正在以" + speed + " 速度运行");}}static class BWM implements ICar {@Overridepublic String name() {return "BWM";}@Overridepublic void run(int speed) {System.out.println("BWM正在以" + speed + " 速度运行");}}
在使用的过程中,我们可以不在原来子类实现,而是使用抽象类实现
IDriver tom = new Driver();// tom 可以驾驶BWMtom.drive(new BWM(), 100);// tom 也可以驾驶BenZtom.drive(new BenZ(), 90);
3.1.3 DIP 对并行开发的影响
上层代码逻辑修改为依赖其抽象之后,只要制定两个类的接口或者抽象类即可,而项目直接的单元测试也可以独立的运行,TDD (测试驱动开发) 就是对DIP最高级的应用。
3.2 依赖的方式
结合Spring的Bean的依赖注入方式可以知道,依赖注入主要有三种方式:
- 构造注入,在构造的时候注入依赖
- Setter方法注入
- 接口方法中注入(汽车的例子使用的就是此方法)
3.3 最佳实践
需要实现依赖倒置,需要有几个必要的基本条件:
- 每个类尽量需要有抽象类或者接口,这是实现依赖倒置的基本条件
- 变量的表面类型(参考JVM的表面类型和实际类型) 应该尽量是接口或者抽象类
- 任何类都不应该从具体类派生
- 尽量不要复写抽象类的方法,类之间依赖的是抽象,修改了抽象对整个代码的稳定性产生影响
- 结合LSP(里式替换原则)
