1. 无环依赖原则
包之间的依赖结构必须是一个直接的无环图形(DAG)。也就是说,在依赖结构中不允许出现环(循环依赖)。
包的依赖
如果一个包A 中的类引用了包B中的类,我们称包A依赖包B。
“依赖”在具体的程序语言中表现为,如果A依赖B,C/C++语言则在A包的文件/类中通过#include语句包含B包中的文件/类;Java语言则A包的类中通过import语句引入B包中的类。
图1(包A依赖包B)
虚线表示一种依赖关系,箭头表示依赖的方向,箭头所在的一侧就是被依赖的包。
包的循环依赖
我们上面讨论了并用图形表示了包之间的依赖关系。如果存在2个或2个以上的包,它们之间的依赖关系图出现了环状,我们就称包之间存在循环依赖关系。
也就是说它们的依赖结构图根据箭头的方向形成了一个环状的闭合图形。如图:
图2:包的循环依赖
如图:A依赖B,B依赖C,C依赖A,形成了一个环状依赖。
包是一个比较合适的发布粒度,当修改了包中的代码(类,模块等)并发布新的版本时,我们需要把该包以及它所依赖的其它包一起发布。发布之后,还需要验证系统是否能在新发布的版本下正常运作。
如果多个包之间形成了循环依赖,比如如图2,A依赖B,B依赖C,C依赖A,我们修改了B并需要发布B的一个新的版本,因为B依赖C,所以发布时应该包含C,但C同时又依赖A,所以又应该把A也包含进发布版本里。
也就是说,依赖结构中,出现在环内的所有包都不得不一起发布。它们形成了一个高耦合体,当项目的规模大到一定程度,包的数目变多时,包与包之间的关系便变得错综复杂,各种测试也将变得非常困难,常常会因为某个不相关的包中的错误而使得测试无法继续。而发布也变得复杂,需要把所有的包一起发布,无疑增加了发布后的验证难度。
如果包的依赖形成了环状结构,怎么样打破这种循环依赖呢?
有2种方法可以打破这种循环依赖关系:第一种方法是创建新的包,第二种方法是使用DIP(依赖倒置原则)和ISP(接口分隔原则)设计原则。
方法一:创建新的包
比如对于图2这种依赖结构:
图2:包的循环依赖
包C要依赖包A,必定A中包含有A,C共通使用的类,把这些共同类抽出来放在一个新的包D里。这样就把C依赖A变成了C依赖D以及A依赖D,从而打破了循环依赖关系。如图:
这样,包的依赖关系就从A->B->C->A变成了:
A->B->C->D
A->D
方法二:DIP与ISP设计原则
ISP(接口分隔原则)可以剔除美用到的接口。DIP(依赖倒置原则)在类的调用之间引入抽象层。
如图,,包A依赖包B(因为包A中的类U使用了包B中的类X);反过来,包B又依赖包A(因为包B中的类Y使用了包A中的类V)
包A,包B之间就形成了一种循环依赖。
我们使用DIP设计原则为V抽象一个接口IVforY,并把该接口放在B包中。
这样就把Y对V的调用转化为:
V继承IVforY
Y调用IVforY
如图:
这样一来,包B中的类就不依赖任何包A中的类了。
无环依赖原则(ADP)为我们解决包之间的关系耦合问题。在设计包结构时,不能有循环依赖。