1 概述
本文主要讲述6个原则。前3个原则关注包的内聚性,这些原则能够指导我们对类组包,后3个原则关注包的耦合性,帮助我们确定包之间的相互关系。最后两个原则为包的设计提供了定量的分析方法。
2 如何进行包的设计
在进行设计包的时候一般我们会提出如下问题:
- 在向包中分配类时应该依据什么原则?
- 应该使用什么设计原则来管理包之间的关系?
- 包的设计应该先于类呢(自顶向下)?还是类的设计应该先于包(自底向上)?
- 如何实际表现出“包”?在程序语言中如何表现(Java、C++)?在某种开发环境中又如何表现?
- 包创建好后,我们应当将它们用于何种目的?
针对上述问题提出6种原则来指导包的设计。
2.1 粒度:包的内聚性原则(REP、CRP、CCP)
包的内聚性原则可以帮助开发者决定如何将类划分到包种。这些原则依赖于这样的事实:至少已经存在一些类,并且它们之间的相互关系也已经确定。因此,这些原则根据“自底向上”的观点对类进行划分的。
2.1.1 重用发布等价原则(REP)
重用的粒度就是发布的粒度,REP指出,一个包的重用粒度可以和发布粒度一样大。程序中重写的任何东西都必须同时被发布和跟踪。
2.1.2 共同重用原则(CRP)
一个包中的所有类应该是共同重用的。如果重用了包中的一个类,那么就要重用包中的所有类,CRP可以帮助我们决定哪些类应该放进同一个包中。它规定了趋于共同重用的类应该属于同一个包。同时,CRP还规定了相互之间没有紧密联系的类不应该在同一个包中。
2.1.3 共同封闭原则(CCP)
包中的所有类对于同一类性质的变化应该是共同封闭的。一个变化若对一个包产生影响,则将对该包中的所有类产生影响,而对于其他的包不造成任何影响。,这个原则是对单一职责原则对于包的重新规定。正如SRP规定的一个类不应该包含多个引起变化的原因那样,这条原则规定了一个包不应该包含多个引起变化的原因。
CCP鼓励我们把可能由于同样的原因而更改的所有类共同聚集在同一个地方。如果两个类之间有非常紧密的绑定关系,不管是物理上的还是概念上的,那么它们总是会一同进行变化,因而它们应该属于同一个包中。这样做会减少软件的发布、重新验证、重新发行的工作量。
2.2 稳定性:包的耦合性原则(ADP、SDP、SAP)
2.2.1 无环依赖原则(ADP)
在包的依赖关系图中不允许存在环。 因为在开发环境中存在许多开发人员都在更改相同的源代码文件集合的情况,所以会发生“晨后综合症”(工作一整天,终于完成了某项功能后回家,第二天早上一来却发现那项功能不再工作了)。
解决上述问题的方法有两个:每周构建、消除依赖环。
-
每周构建
它的工作方式为:在一周的前四天,所有的开发人员互不打扰,工作在各自私有的那份代码上,而不担心互相之间的集成问题。周五,它们集成进各自的更改并构建系统。
这样做起初项目小的时候没什么明显的问题,随着项目的增长,集成的工作量会一直增加到需要周六加班才能完成。只需几次这样的周六加班,开发人员就会认为其实应该在周四开始集成。这样,集成的起始时间就会慢慢蔓延至一周的中期。
随着开发和集成时间比率的降低,团队效率也随之降低,最后,这会非常的令人沮丧,以至于开发人员或者项目管理者宣称应该把构建安排为每两周一次。这种做法暂时可以应付一下,但是集成的时间仍然不断地随着项目规模一起增长。
最终导致项目危机,为了保持效率,就必须要不断地延长构建周期。但是,延长构建周期会增加项目的风险。集成和测试变得越来越难进行,团队也会丧失了快速反馈带来的好处。 -
消除依赖环
在任何情况下,都可以解除包之间的依赖环并把依赖关系图恢复为一个DAG。主要有两个办法:
(1)使用依赖倒置原则(DIP),可以创建一个被依赖包需要的接口的抽象类。让依赖包从这个抽象类继承,这样依赖包就依赖了抽象的类而不是具体的实现细节,从而倒置了被依赖包和依赖包之间的依赖关系,解除了依赖环。
(2)新建包,如果依赖之间形成了环,可以新建一个包将公共依赖的类都放在这个包中,从而解除了依赖环。
2.2.2 稳定依赖原则(SDP)
朝着稳定的方向进行依赖。在进行包的设计的时候,让包的依赖关系朝着稳定的方向进行。
稳定性
- 稳定的包
- 一个不稳定的包
稳定性的度量
稳定性通过如下计算公示可以得到
- (Ca)输入耦合度(Afferent Coupling):指处于该包的外部并依赖于该包的类的类的数目。
- (Ce)输出耦合度(Efferent Coupling):指处于该包的内部并依赖于该包外的类的类的数目。
该度量的取值范围是[0,1]。I=0表示该包具有最大的稳定性。I=1表示该包具有最大的不稳定性。
SDP规定一个包的I度量值应该大于它所依赖的包的I度量值(也就是说,I度量值应该顺着依赖的方向减少)
2.2.3 稳定抽象原则(SAP)
概念
包的抽象程度应该和其稳定程度一致。该原则把包的稳定性和抽象性联系起来。它规定,一个稳定的包应该也是抽象的,这样它的稳定性就不会使其无法扩展。另一方面,它规定,一个不稳定的包应该是具体的,因为它的不稳定性使得其内部的具体代码易于更改。
因此,如果一个包是稳定的,那么它应该也要包含一些抽象类,这样就可以对它进行扩展。可扩展的稳定包是灵活的,并且不会过分限制设计。
SAP和SDP结合在一起形成了对包的DIP原则。这样说是准确的,因为SDP规定依赖应该朝着稳定的方向进行,而SAP则规定稳定性意味着抽象性。因此,依赖应该朝向抽象的方向进行。
然而,DIP是一个处理类的原则。类没有灰度的概念。一个类要么是抽象的,要么不是。SDP和SAP的结合是处理包的,并且允许一个包是部分抽象、部分稳定的。
抽象性度量
- Nc——包中类的总数
- Na——包中抽象类的数目。请记住,一个抽象类是一个至少具有一个纯接口(pure interface)的类,并且它不能被实例化。
- A——抽象性
度量A的取值范围是从0到1。0意味着包中没有任何抽象类。1意味着包中只包含抽象类。
2.3 A-I 坐标图
上两个部分讲述了稳定性和抽象性的度量。现在可以创建一个以A为纵轴,I为横轴的坐标图。我们发现(0,1)附近区域最稳定、最抽象。(1,0)附近区域最不稳定,最具体。具体如下图所示:
(0,1)到(1,0)这条直线是主序列,一般包的抽象度和稳定度位于这条线附近。
(0,0)附近的包最稳定也最具体,这附近的包是僵化的,无法对它进行扩展。
(1,1)附近的包最抽象也最不稳定,没有依赖者,所以被称为无用区。
显然,包的最佳位置位于主序列的两个端点处(0,1)和(1,0)。根据实际项目经验来看,具有这种最佳特征的包的数量少于一半。对于包来说,它们能够位于这条主序列的附近就已经很不错了。根据包所在的位置到这条主序列的距离引出了一个D的概念,这个D是用来衡量包到这个理想位置的距离。
D——距离,取值在[0, -0.707]
D’——规范化的距离,取值在[0,1]
D’的概念可以绘制一个D’随时间变化的分布图,来监控包是否偏离主序列过多。
3 结论
本文描述的依赖性管理度量可以测试一个设计与“好”的依赖、抽象结构模式间的匹配程度。经验表明,依赖关系是有好坏之分的。该模式反应了这种经验。然而,度量不是万能的;它只是一个取代随意标准的测量方法。文中选择的标准可能只对某些应用程序合适,而对另外一些则不合适。同样也可能存在有更好的测量设计质量的度量方法。
下图为使用该方法绘制的包的D值分布图。