OOP设计原则之依赖倒置原则(DIP)

by:leotse

Program to an interface, not an implementation.

先不管DIP的定义,我们看一个例子,我们的APP需要使用Facebook Audience Network的广告实现应用内变现,一般的实现方式是(这里用Java实现):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class AdController{
...
public void display(){
FacebookAdManager fbAdManager = new FacebookAdManager();
fbAdManager.displayFbAd();
}
}

...

public class FacebookAdManager{
public void displayFbAd(){
...
}
}

这样的实现方式比较常见。这里我们抽象出两个模块,控制模块(AdController)以及广告加载模块(FacebookAdManager),两个模块之间的关系是处于高层的控制模块直接依赖于低层的广告加载模块,这也是我们在传统的应用架构中见得比较多的一种代码依赖关系,就像楼房的高层需要基于低层一样。

我们能发现,这样的依赖关系使得这两个相对比较独立的模块紧紧耦合在一起,如果我们我们需要修改任一模块的逻辑,那么很有可能会影响另一模块的业务代码,比如:我们由于广告策略调整,还想借助于Google家的Admob平台实现广告变现,这时候,原来的业务逻辑就会变成:

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
public class AdController{
...
public void display(){
FacebookAdManager fbAdManager = new FacebookAdManager();
fbAdManager.displayFbAd();
AdmobAdManager amAdManager = new AdmobAdManager();
amAdManager.displayAmAd();
}
}

...

public class FacebookAdManager{
public void displayFbAd(){
...
}
}

...

public class AdmobAdManager{
public void displayAmAd(){
...
}
}

这样的代码就会显得比较笨拙,广告加载模块(FacebookAdManager和AdmobAdManager)的每一次调整,或者控制模块的每一次策略调整,都有可能影响另一个模块的业务。那么我们怎么更加优雅地实现呢?我们在两个模块之间抽象出一个接口:

1
2
3
public interface IAdManager{
public void displayAd();
}

我们将Admob和Facebook广告共同的方法抽象出来,这样所有的广告源的实现变为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class FacebookAdManager implements IAdManager{
@Override
public void displayAd(){
...
}
}

...

public class AdmobAdManager implements IAdManager{
@Override
public void displayAd(){
...
}
}

如此以来,控制模块的调用逻辑就会变得比之前的方式更加灵活:

1
2
3
4
5
6
7
8
9
10
11
public class AdController{
...
public void display(){
displayController(new AdmobAdManager());
displayController(new FacebookAdManager());
}

public void displayController(IAdManager adManager){
adManager.displayAd();
}
}

总结一下上面的示例,高层模块(AdController)本来需要直接依赖于低层模块(FacebookAdManager和AdmobAdManager),但是我们为了提高可扩展性以及降低模块之间的耦合,我们加入了抽象接口层(IAdManager),使得高层模块不直接依赖于低层模块,而是两者都依赖于该接口层,这种设计原则我们称之为依赖倒置原则(Dependence Invesion Principle,DIP)。(这里的倒置并不是说低层模块反过来依赖于高层模块)

一般介绍DIP都会提到这样两个关键点:
1)高层模块不应该直接依赖于低层模块,他们都应该依赖于抽象
高层模块和低层模块都应该通过抽象来进行解耦。
2)抽象不应该依赖于细节,而细节应该依赖于抽象
这里的抽象指的是接口或者抽象类,而细节指的是这些接口或者抽象类的具体实现。这个不难理解,抽象就相当于两个模块之间的规则,实现就是遵照这些规则的具体操作,很显然具体操作是需要依赖于既定规则的。

对于DIP,我们可以这样总结:
细节是多变的,抽象是稳定的。