面向对象的六大原则(四)

依赖倒置原则的定义

依赖倒置原则的英文为Dependence Inversion Principle,简称就是DIP。原始的定义为:
High level modules should not depend upon low level modules.Both should depend upon abstractions.Abstractions should not depend upon details.Details should depend upon abstractions.

翻译过来就是:

  • 高层模块不应该依赖底层模块,他们都应该依赖抽象;
  • 抽象不应该依赖细节;
  • 细节应该依赖抽象;

对应的,依赖倒置原则在Java中的表现是:

  • 模块间的依赖通过抽象发生,实现类之间不发生直接的依赖关系,其依赖关系是通过接口或抽象类产生的;
  • 接口或抽象类不依赖于实现类;
  • 实现类依赖接口或抽象类;
    更加精简的定义就是“面向接口编程”。

依赖倒置原则的示例

先来看一个示例,小明家里有一辆大众汽车,小明每天开车去上班。类图如下:
ShowImage

小明有一个方法drive()可以驾驶,大众车有一个方法run()来运行,代码如下:

1
2
3
4
5
6
public class Driver {
//小明驾驶的方法
public void drive(Volkswagen volkswagen) {
volkswagen.run();
}
}

1
2
3
4
5
6
public class Volkswagen {
//汽车运行的方法
public void run() {
System.out.println("大众汽车运行...");
}
}
1
2
3
4
5
6
7
8
public class Work {
public static void main(String[] args) {
Driver xiaoMing = new Driver();
Volkswagen volkswagen = new Volkswagen();
//小明开大众车上班
xiaoMing.drive(volkswagen);
}
}

这样子小明就可以开他自己的大众车去上班了,有一天小明的车坏了,送到汽车厂维修,没办法,只有借他父亲的宝马车一用。
宝马车代码

1
2
3
4
5
6
public class BMW {
//宝马车运行的方法
public void run() {
System.out.println("宝马汽车运行...");
}
}

小明拿到父亲的宝马车了,但是发现自己怎么样也不能开动车子,小明的驾驶方法里面只有开动大众车的方法,要让宝马车开动起来,我们还要去修改驾驶员类,这样也太不合理了。那么怎么办呢,现在我们通过依赖倒置原则,让车和驾驶员之间通过接口来依赖。

我们重新设计一下,类图如下:
ShowImage

司机接口

1
2
3
4
public interface IDriver {
//所有司机都应该能驾驶汽车
void drive(ICar car);
}

司机的实现类

1
2
3
4
5
6
public class Driver implements IDriver {
//司机开车的方法
public void drive(ICar car) {
car.run();
}
}

这样在IDriver中,通过传入ICar接口实现了抽象之间的依赖关系,Driver实现类传入的是ICar接口,至于是哪一种车,需要在更高层的模块来声明。

汽车接口

1
2
3
4
public interface ICar {
//汽车运行
void run();
}

大众汽车实现类

1
2
3
4
5
6
public class Volkswagen implements ICar {
//大众车运行的方法
public void run() {
System.out.println("大众汽车运行...");
}
}

宝马汽车实现类

1
2
3
4
5
6
public class BMW implements ICar {
//宝马车运行的方法
public void run() {
System.out.println("宝马汽车运行...");
}
}

现在再来看一下上班过程的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Work {
public static void main(String[] args) {
IDriver xiaoMing = new Driver();
ICar volkswagen = new Volkswagen();
ICar bmw = new BMW();
//小明开他自己的大众车上班
xiaoMing.drive(volkswagen);
//小明开他父亲的宝马车上班
xiaoMing.drive(bmw);
}
}

修改了设计之后,汽车和驾驶员之间的强耦合关系就解开了,它们之间通过接口进行依赖,这样的话,如果我们以后要增加汽车Benz类,只需要让Benz类实现ICar接口就可以使用了,这样也不用去修改其他的类和接口,依赖倒置原则的好处就在这里体现出来了。

依赖的三种实现方式

对象的依赖关系有三种方式来传递,分别是接口传递,构造函数传递和setter方法传递

接口声明依赖对象

在上面的例子中,就采用了接口声明依赖的方式,这种方法也叫做接口注入。

构造函数传递依赖对象

在类中通过构造函数来声明依赖对象,按照依赖注入的说法,这种方法叫做构造函数注入,按照这种方式写的IDriver和Driver如下:

1
2
3
4
public interface IDriver {
//司机开车
void drive();
}

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Driver implements IDriver {
private ICar car;
//构造函数注入
public Driver(ICar car) {
this.car = car;
}
//司机驾驶汽车
public void drive() {
this.car.run();
}
}

Setter方法传递依赖对象

在抽象中设置Setter方法声明依赖关系,这种方法也叫做Setter依赖注入,按照这种方式写的IDriver和Driver如下:

1
2
3
4
5
6
7
public interface IDriver {
//设置车辆型号
void setCar(ICar car);
//司机驾驶汽车
void drive();
}

1
2
3
4
5
6
7
8
9
10
11
12
public class Driver implements IDriver {
private ICar car;
public void setCar(ICar car) {
this.car = car;
}
//司机驾驶汽车
public void drive() {
this.car.run();
}
}

依赖倒置原则的总结

优点

  • 降低了类之间的耦合性;
  • 提高系统的稳定性;
  • 降低并行开发的风险;
  • 提高代码的可读性和可维护性;
  • 减少需求变化时原有代码大量改变情况的出现;

实践

要做到依赖倒置原则,可以从以下几个方面入手:

  • 每个类尽量都有接口或抽象类,或者接口和抽象类都有
  • 变量的表面类型尽量是接口或者是抽象类
  • 类不应该从具体类派生
  • 尽量不要覆写基类的方法
  • 结合里氏替换原则使用

参考代码

------ 本文结束 ------

版权声明


BillyYccc's blog by Billy Yuan is licensed under a Creative Commons BY-NC-SA 4.0 International License.
本文原创于BillyYccc's Blog,转载请注明原作者及出处!