依赖倒置原则是系统解耦的重要原则,遵循它可以让我们的系统更加健壮。
依赖倒置原则(Dependency Inversion Principle)是 Robert C. Martin 提出的,原则声明了两个方面:
依赖倒置原则的声明中有几个概念:上层、下层,抽象、实现。
上层、下层是一类概念,在计算机的设计中,分层是常见的任务分解方法,每一层都使用下层提供的功能,又为更上层提供自己的功能。依赖倒置原则要求在设计层间通信、交互标准时,不应依赖于某个下层,而是应该依赖于抽象,这样上下层之间就没有强耦合,如果两个实现都遵循了同样的抽象,则可以在上层无感知的情况下替换下层实现。
抽象、实现是一类概念,抽象是对同一类任务本质属性的表达,实现则是具体每一类任务的细节的表达。依赖倒置原则说明实现应该依赖于抽象,是因为实现是对抽象骨架的填充,而抽象不应依赖于实现,是因为抽象是对本质的归纳,应去掉细节的干扰。
看了上面的规则,还需要问一下依赖倒置原则倒置了什么呢?
从字面看依赖倒置当然是倒置了依赖,但核心是控制权的反转。我们从下面的例子来解释。
需求要求实现一个排序系统,系统需要实现各种排序算法,使用方可以根据需要调用不同的排序算法来对自己的数据进行排序。
设计的接口如下:
public interface Sort {
public void sort(int[] nums);
}
有不同的排序算法实现:
public class QuickSort {
public void sort(int[] nums) {
//quick sort implementation
...
}
}
public class MergeSort {
public void sort(int[] nums) {
//merge sort implementation
...
}
}
public class BubbleSort {
public void sort(int[] nums) {
//bubble sort implementation
...
}
}
......
使用方使用:
public class Client {
public static void main(String[] args) {
Sort sort = new QuickSort();
int[] nums = new int[10];
// initial nums
sort.sort(nums);
}
}
可以看到,使用方在使用的时候依赖的是抽象的Sort
接口,但是接口是没法实例化的,因此第一句Sort sort = new QuickSort();
将实现实例化后赋值给变量,这里,使用方作为上层模块就依赖了下层实现,违反了依赖倒置原则。
要解决这个问题,需要将实例化的过程迁移到排序系统中,使用方通过配置、参数等方式选择自己要使用的算法,这样使用方就不依赖排序系统具体的实现,而只依赖于Sort
的接口抽象。
我们实现一种由排序系统智能判断返回排序算法的简单工厂。
public final class SortFactory {
private static final int SIZE_THRESHOLD = 300;
public static Sort choose (int sortSize) {
if (sortSize < SIZE_THRESHOLD) {
return new BubbleSort();
}
return new QuickSort();
}
}
使用方在在使用时直接使用 Sort sort = SortFactory.choose(size)
的形式来获得排序实例即可。
从这个例子中,我们可以看到,实例化的控制权原本是在使用方的手中,但这样就将抽象与实现耦合在了一起,后面我们使用简单工厂模式将控制权交回到排序系统,使用方就只需要调用工厂方法来获取实例即可而无需关心具体实现了。
在考虑依赖倒置原则的使用时,跟单一职责原则一样,需要注意使用的粒度。如果所有的代码都符合依赖倒置原则,那就过犹不及了。
依赖倒置原则要求系统建立在抽象的基石之上,而不是实现的浮土之上。需求的变化是迅速而猛烈的,相应的就要求实现也是随时随地变化的,而其中的本质抽象却是相对不变的,如此系统就可以保持健壮,不会因外部纷扰左右摇摆。