三、依赖倒置原则
定义
依赖倒置原则(Dependence Inversion Principle ,DIP):高层模块不应该依赖低层模块,应该依赖其抽象,抽象不应该依赖其细节,细节应该依赖其抽象。
理解:低层模块:具体细化的 Java 类。高层模块:是由多个低层模块组成的。抽象:指的是接口或者抽象类。依赖:存在类 A 的一个方法 S, S 传入的参数是另一个类 B 的实例,那么类 A 依赖于类 B, 也就是类 A 中引用了类 B, 则 A 依赖 B, 因为 A 类中缺少了 B 类就无法正常运行了!
实例
先举一个反例子:一个司机开宝马车。正常思维是定义一个司机类 Driver, 并实现一个开车 void drive(BMWCar bmwcar)
的方法,该方法传入的是宝马车的一个实例!
1 | //宝马车类,实现run()方法 |
运行结果
1 | 司机开车... |
结果得到没有问题!但现在我们更改需求了,司机现在改开奔驰了,那么如果在这个类的基础上更改,我们需要给司机提供一个 drive(BenCar bencar)
的方法。而如果后续要司机开各种车难道都要实现一个方法吗?这不免造成依赖性太强和冗余过度情况!
现在的解决方案:定义接口 ICar, 让 BMWCar 等其他车类都实现 ICar 接口,而司机 Driver 类只需在 drive(ICar car)
的方法里传入 ICar 类型即可实现司机开各种车型的功能!
1 | public interface ICar { |
司机Driver类
1 | public class Driver{ |
上面就遵循了依赖倒置原则,另外上面依赖的传递是通过参数直接传的!实际上依赖传递分为三种情况:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29 //1、通过构造方法传递
class Driver{
private ICar car;
public Driver2(ICar car){
this.car = car;
}
public void drive() {
System.out.println("司机发动车了...");
car.run();
}
}
//2、通过setter方法传递
class Driver{
private ICar car;
public void drive() {
System.out.println("司机开车.....");
car.run();
}
public void setCar(ICar car) {
this.car = car;
}
}
//3、通过接口传递(也就是我们上面例子的使用方法)
class Driver{
public void drive(ICar car) {
System.out.println("司机发动车了...");
car.run();
}
}
结论
在实际编程中,我们一般需要做到如下3点:
1、低层模块尽量都要有抽象类或接口,或者两者都有。
2、变量的声明类型尽量是抽象类或接口。
3、使用继承时遵循里氏替换原则。
依赖倒置原则的核心就是要我们面向接口编程,理解了面向接口编程,也就理解了依赖倒置。
四、接口隔离原则
定义
接口隔离原则 ( Interface Segregation Principle ): 客户端不应该依赖它不需要的接口;一个类对另一个类的依赖应该建立在最小的接口上。
理解:将臃肿的接口根据其他类需要使用的方法拆分为独立的几个接口。也就是采用接口隔离原则。实际上该原则与单一职责原则有点相似,都是将方法拆分成多份!不同点是单一职责通常适用于类,并且根据职责拆分;而接口隔离原则主要适用于接口,根据其他类的使用需求拆分!
实例
首先定义了一个接口 I, 并定义 5 个方法, 并定义类 A 类 C, 这两个类依赖 I;类A依赖于接口I中的 method1(), method2(), method3()三个方法;类 C 依赖接口I中的 method1(), method()4, method5() 三个方法。
类 B, 类 D 则是 A, B 依赖接口I的具体方法实现。
1 | interface I { |
可以看到,如果接口过于臃肿,只要接口中出现的方法,不管对依赖于它的类有没有用处,实现类中都必须去实现这些方法,这显然不是好的设计。如果将这个设计修改为符合接口隔离原则,就必须对接口 I 进行拆分。
我们将接口 I 拆分为三个接口 I1, I2, I3; 而类 B, 类 D 的实现则不用完全实现 I 了,而只需实现自己需要的方法接口了!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56 interface I1 {
public void method1();
}
interface I2 {
public void method2();
public void method3();
}
interface I3 {
public void method4();
public void method5();
}
class B implements I1, I2{
public void method1() {
System.out.println("类B实现接口I1的方法1");
}
public void method2() {
System.out.println("类B实现接口I2的方法2");
}
public void method3() {
System.out.println("类B实现接口I2的方法3");
}
}
class D implements I1, I3{
public void method1() {
System.out.println("类D实现接口I1的方法1");
}
public void method4() {
System.out.println("类D实现接口I3的方法4");
}
public void method5() {
System.out.println("类D实现接口I3的方法5");
}
}
//同时A,C中的方法参数类型改为对应的接口即可!
class A{
public void depend1(I1 i){
i.method1();
}
public void depend2(I2 i){
i.method2();
}
public void depend3(I2 i){
i.method3();
}
}
class C{
public void depend1(I1 i){
i.method1();
}
public void depend2(I3 i){
i.method4();
}
public void depend3(I3 i){
i.method5();
}
}
本文例子中,将一个庞大的接口变更为3个专用的接口所采用的就是接口隔离原则。在程序设计中,依赖几个专用的接口要比依赖一个综合的接口更灵活。接口是设计时对外部设定的“契约”,通过分散定义多个接口,可以预防外来变更的扩散,提高系统的灵活性和可维护性。
结论
接口隔离原则的含义是:建立单一接口,不要建立庞大臃肿的接口,尽量细化接口,接口中的方法尽量少。也就是说,我们要为各个类建立专用的接口,而不要试图去建立一个很庞大的接口供所有依赖它的类去调用。
采用接口隔离原则对接口进行约束时,要注意以下几点:
1、接口尽量小,但是要有限度。对接口进行细化可以提高程序设计灵活性是不挣的事实,但是如果过小,则会造成接口数量过多,使设计复杂化。所以一定要适度。
2、为依赖接口的类定制服务,只暴露给调用的类它需要的方法,它不需要的方法则隐藏起来。只有专注地为一个模块提供定制服务,才能建立最小的依赖关系。
3、提高内聚,减少对外交互。使接口用最少的方法去完成最多的事情。