重学Java设计模式
上QQ阅读APP看本书,新人免费读10天
设备和账号都新为新人

2.6 依赖倒置原则

2.6.1 依赖倒置原则定义

依赖倒置原则是Robert C.Martin于1996年在C++Report上发表的文章中提出的。

依赖倒置原则(Dependence Inversion Principle,DIP)是指在设计代码架构时,高层模块不应该依赖于底层模块,二者都应该依赖于抽象。抽象不应该依赖于细节,细节应该依赖于抽象。

依赖倒置原则是实现开闭原则的重要途径之一,它降低了类之间的耦合,提高了系统的稳定性和可维护性,同时这样的代码一般更易读,且便于传承。

2.6.2 模拟场景

在互联网的营销活动中,经常为了拉新和促活,会做一些抽奖活动。这些抽奖活动的规则会随着业务的不断发展而调整,如随机抽奖、权重抽奖等。其中,权重是指用户在当前系统中的一个综合排名,比如活跃度、贡献度等。

下面模拟出抽奖的一个系统服务,如果是初次搭建这样的系统会怎么实现?这个系统是否有良好的扩展性和可维护性,同时在变动和新增业务时测试的复杂度是否高?这些都是在系统服务设计时需要考虑的问题。

2.6.3 违背原则方案

下面先用最直接的方式,即按照不同的抽奖逻辑定义出不同的接口,让外部的服务调用。

1.定义抽奖用户类

这个类就是一个普通的对象类,其中包括了用户姓名和对应的权重,方便满足不同的抽奖方式。

接下来实现两种不同的抽奖逻辑,在一个类中用两个接口实现,如下所示。

在这个抽奖逻辑类中包括了两个接口,一个是随机抽奖,另一个是按照权重排序。

·随机抽取好理解,把集合中的元素使用工具包Collections.shuffle()进行乱序,之后选取三个元素。当然,除了这样的随机抽取方式,还有其他方式。

·按照权重排序,这里使用了list.sort的方法,并按排序逻辑的方式进行自定义排序。最终选择权重最高的前三名作为中奖用户。

2.测试结果

这里使用单元测试的方式,在初始化数据后分别调用两个接口方法进行测试。测试结果如下所示。

从测试结果上看,程序没有问题,验证结果正常。但是这样实现有什么问题呢?

如果程序是一次性的、几乎不变的,那么可以不考虑很多的扩展性和可维护性因素;但如果这些程序具有不确定性,或者当业务发展时需要不断地调整和新增,那么这样的实现方式就很不友好了。

首先,这样的实现方式扩展起来很麻烦,每次扩展都需要新增接口,同时对于调用方来说需要新增调用接口的代码。其次,对于这个服务类来说,随着接口数量的增加,代码行数会不断地暴增,最后难以维护。

2.6.4 依赖倒置原则改善代码

既然上述方式不具备良好的扩展性,那么用依赖倒置、面向抽象编程的方式实现。

首先定义抽奖功能的接口,任何一个实现方都可以实现自己的抽奖逻辑。

1.抽奖接口

这里只有一个抽奖接口,接口中包括了需要传输的 list 集合,以及中奖用户数量。

2.随机抽奖实现

这部分随机抽奖逻辑与上面的抽奖方式逻辑是一样的,只不过放到接口实现中了。

3.权重抽奖实现

权重抽奖也是一样,把这些都放到自己的接口实现中。这样一来,任何一种抽奖都有自己的实现类,既可以不断地完善,也可以新增。

4.创建抽奖服务

在这个类中体现了依赖倒置的重要性,可以把任何一种抽奖逻辑传递给这个类。这样实现的好处是可以不断地扩展,但是不需要在外部新增调用接口,降低了一套代码的维护成本,并提高了可扩展性及可维护性。

另外,这里的重点是把实现逻辑的接口作为参数传递,在一些框架源码中经常会有这种做法。

5.测试结果

这里与前面代码唯一不同的是新增了实现抽奖的入参 new DrawRandom()、new DrawWeightRank()。在这两个抽奖的功能逻辑作为入参后,扩展起来会非常的方便。

以这种抽象接口为基准搭建起来的框架结构会更加稳定,算程[1]已经建设好,外部只需要实现自己的算子[2]即可,最终把算子交给算程处理。


[1] 算程是一段算法的执行过程。

[2] 算子是具体算法的执行逻辑。