包组织原则
- 将在功能上有紧密联系的、垂直或水平的切片打包
- 将一族接口打包
- 将一组不稳定的类打包
- 提取独立的类型
- 利用工厂(factory)来降低实体包之间的依赖
- 不要在包中出现回路
- 在一个类里发生变化会影响到另一个类;
- 移除一个类将会影响另一个类;
- 两个类之间有复杂的内部交互或是互相传递大量的信息;
- 如果一个边界类(boundary class)中的一个函数是用来呈现某个实体类的,那么这个边界类将和这个实体类有功能性关联;
- 两个类被同一个因素影响或产生交互;
- 两个类之间有关系;
- 一个类创建另一个类的实体;
- 两个类与不同的因素有关系;
- 一个可选类和一个强制类不应该放在同一个包里;
包设计原则
发布重用原则(REP)
- 包通常是一个发布版本中的基本单位;
- 为了提供重用所需要的保证,开发人员必须将他们的软件分成一些可重用的包,然后跟踪这些包,再发布新的版本;
- 我们重用的任何东西都必须被发布并且跟踪;
- 一个包中的元素或类要么都可重用,要么都不可重用。这是因为如果一个包里包含了某个可以重用的软件,它就不应该再包括不是用于重用的软件。
重用发布等价原则为我们指明了包的设计方针:
一个包中的元素(类)要么都可重用,要么都不可重用。
全部重用原则(CRP)
公共闭合原则(CCP)
非循环依赖原则(ADP)
ADP规定,The dependency structure between packages must be a directed acyclic graph (DAG). That is, there must be no cycles in the dependency structure.包之间的依赖结构必须是一个直接的无环图形(DAG)。也就是说,在依赖结构中不允许出现环(循环依赖)。
如果出现了包循环依赖问题,如包A依赖包B,包B依赖包C,而包C又依赖包A,那么我们修改了B并需要发布B的一个新的版本,因为B依赖C,所以发布时应该包含C,但C同时又依赖A,所以又应该把A也包含进发布版本里。也就是说,依赖结构中,出现在环内的所有包都不得不一起发布。它们形成了一个高耦合体,当项目的规模大到一定程度,包的数目变多时,包与包之间的关系便变得错综复杂,各种测试也将变得非常困难,常常会因为某个不相关的包中的错误而使得测试无法继续。而发布也变得复杂,需要把所有的包一起发布,无疑增加了发布后的验证难度。
为了打破这种循环依赖,有两种解决方法:
1)找出两个包里参与这个循环的因素,并把它们组合成一个新的包。
例如,若存在以下依赖关系。
包C要依赖包A,必定A中包含有A,C共通使用的类,把这些共同类抽出来放在一个新的包D里。这样就把C依赖A变成了C依赖D以及A依赖D,从而打破了循环依赖关系。如图:
这样,包的依赖关系就从A->B->C->A变成了:
A->B->C->D
A->D
2)利用接口。ISP(接口分隔原则)可以剔除没用到的接口。DIP(依赖倒置原则)在类的调用之间引入抽象层。
例如,若存在左下图的循环依赖。
那么A必然使用了B中的某个类,这样我们可以吧这个类分离成一个接口,并让B实现这个接口并依赖于它,同时让包A也依赖于这个接口,就可以打破这种循环。
又例如,三个循环依赖的类Myapplication、MyTasks和MyDialoges,如左下图。如果用方法一,我们可以将Myapplication和MyDialoges共同使用的类分离成一个新的包,如右下图所示;如果用方法二,MyDialoges中的类X使用了Myapplication中的类Y,我们可以为类Y设计一个接口IX,并把它放在包MyDialogues里,再让Myapplication中的Y实现并该包依赖于它。
稳定依赖原则(SDP)
也就是说,包应该依赖比自己更稳定的包。因为如果依赖一个不稳定的包,那么当这个不稳定的包发生变化时,本身稳定的包也不得不发生变化,变得不稳定了。
所谓稳定,在现实生活中是指一个物体具有稳固不变的属性使它很难发生变化。应用到软件概念上,我们认为一个软件是稳定的,是因为这个软件很难发生改变,或更确切地说,是不需要发生改变。一个设计良好,能应对各种变化不需要修改的软件当然是稳定的了,但事实上,往往一个软件常常需要对应某个事先没有预测到的用户需求而不得不发生改变,当这种改变发生时,能把修改控制在最小的范围之内,并能稳定的工作(包括软件本身以及依赖它的其它软件实体等),我们也会认为该软件是相对稳定的。
那么,怎么判断一个包是否稳定呢?我们可以通过下面的方法来判断一个包的稳定系数:
- Ca:Afferent Coupling。向心耦合。依赖该包(包含的类)的外部包(类)的数目(i.e. incoming dependencies)。
- Ce: Efferent Coupling。离心耦合。被该包依赖的外部包的数目(i.e. outgoing dependencies)。
如图1,X的Ce=0,所以不稳定性I=0,它是稳定的。相反,如图2,Y的Ce=3,Ca=0,所以它的不稳定性I=1,它是不稳定的。
SDP要求一个包的不稳定性I要大于它所依赖的包的不稳定性。“Depend upon packages whose I metric is lower than yours.”
换句话说,沿着依赖的方向,包的不稳定性应该逐渐降低,稳定性应该逐渐升高。
例如,左下图中一个稳定的包依赖了一个不稳定的包,就违反了SDP原则。
稳定抽象原则(SAP)
一个包的抽象程度越高,它的稳定性就越高。反之,它的稳定性就越低。一个稳定的包必须是抽象的,反之,不稳定的包必须是具体的。
稳定的包的构成
抽象类或接口通过子类继承扩展行为,这表示抽象类或接口比它们的子类更具有稳定性。总之,为了构成稳定的包,应该提高包内的抽象类或接口的比率;它们的子类可以放在另一个不稳定的包内,该包依赖上述稳定的包,从而遵循了稳定依赖原则(SDP)。
理想的体系结构应该是:
不稳定的(容易改变的)包处于上层
- 它们是具体的包实现
稳定的(不容易改变的)包处于下层
- 不容易改变,但容易扩展
- 接口比实现(具体的运行代码)在内在特性上更具有稳定性
因此,我们可以1)将具有功能性关联的接口放在同一个包里,并与其实现分离开来;2)利用工厂来降低具体的包之间的依赖,一种提高包的稳定性的方法是减少它对其他包中具体的包类的依赖。
我们通常使用工厂(factory)来降低包之间的耦合度。参考资料:包的设计原则 http://www.uml.org.cn/mxdx/200912233.asp