在Robert C. Martin(Bob大叔)的《敏捷软件开发:原则、模式与实践》(Agile Software Development: Principles, Patterns, and Practices)一书中,他提出了一些用于设计组件(或包)的原则。传统的面向对象设计原则,例如SOLID、CARP、LoD等主要关注类的设计,而下面要介绍的这些原则主要用于设计组件和包的结构,一共包括六个原则:前面三个关注组件的内聚性(Cohesion),用于指导我们如何将类组包;后面三个关注组件的耦合性(Coupling),帮助我们确定组件之间的相互关系。简单来说,组件(或包)的设计也要做到“高内聚,低耦合”。Sunny认为,有些原则描述的是一种理想状况,在实际使用时更多的是尽量趋向于这些原则,通常很难做到百分之百满足这些原则,。
组件内聚性原则:粒度 (Principles of Component Cohesion: Granularity)
组件的内聚性原则帮助开发者决定如何将类划分到组件中。这些原则取决于这样的事实:至少已经存在一些类并且它们之间的相互关系也得以确定。因此,这些原则根据自底向上的观点对类进行划分,先分析类以及关系,再逐步对类进行组织。
重用-发布等价原则(The Reuse/Release Equivalence Principle, REP)
The granule of reuse is the granule of release.
重用的粒度就是发布的粒度。
在实际开发中,我们的重用粒度通常不是类一级的,而是组件一级的,这里所说的组件可以是Java中的jar,也可以是DLL或者共享库等。REP要求我们保持组件的重用粒度(granule of reuse)和组件的发布粒度(granule of release)一致,何谓重用、何谓发布我想大家应该都懂,。由于重用是在组件这一级,因此可重用的组件中必须包含可重用的类,这些可重用的类以组件的方式发布给用户使用。REP要求我们从重用的角度去考虑一个组件的内容,一个组件中的类要么都可以重用,要么都不是可重用的( Either all the classes in a component are reusable, or none of them are.)。例如在一个基于Model 2的Java EE项目中,我们将所有的DAO类打包为一个jar,目的是在其他基于相同数据库的项目中能够重用所有的DAO类,在这个DAO组件中就不应该包含任何控制层(Servlet)或者表示层(JSP等)相关的类,因为这些类不能同时被重用。
此外,在实施REP时,与面向对象设计原则中的SRP(单一职责原则)类似,在一个组件中不应该包含太多不同类型的类,不要把一些完全不相干的类放在一个组件中,这样会导致组件的职责过重,增加修改和发布的频率。因此,我们应该让一个组件中的所有类对于同一类用户或者面向同一场景是可以重用的,不应该让组件中的一部分类对用户而言有用而其他类不适用。
简而言之,REP要求我们从复用的角度来设计组件,让一个组件中所有的类都能够一起被复用,不存在不能复用的类,也不存在只有在另一种场合下才能够被复用的类。复用的粒度即发布组件的粒度。
共同重用原则(The Common Reuse Principle, CRP)
The classes in a component are reused together. If you reuse one of the classes in a component, you reuse them all.
一个组件中的类需一起被重用。如果你重用了组件中的一个类,那么就要重用其中所有的类。
CRP与之前的REP通常会一起出现,同一个组件中的类作为一个整体被复用,而不是只复用其中的某一个或几个类。我个人觉得,CRP是一种理想情况,它有利于降低组件之间的耦合度,但是实现起来有一定的难度,对于一个类稍微多一点的组件,很难同时复用其中所有的类,大部分情况下都只是使用其中的部分类,。我们需要做的是,尽量让这些类位于同一个组件中,减少与客户端程序交互的组件数量,降低系统耦合度。
CRP告诉我们需要将哪些类放在同一个组件中,在略为复杂一点的系统中,类很少会孤立的重用。例如,有时候需要将一个具体类和它的抽象层一起重用,需要将一个聚合类和它的迭代器一起重用,需要将工厂类和产品类一起重用,此时,最好将它们设计在同一个组件中。如果将它们分离开,放在两个不同的组件里,势必会在一些组件之间增加依赖关系,被依赖方的组件发生修改和重新发布时,依赖方的组件也需要重新验证和发布,导致维护和升级工作量增加。
此外,如果一个组件中类太多,只要该组件中一个类发生改变重新发布时,所有依赖这个组件的客户类都应该进行测试验证,看是否引入bug,即使发生修改的类与大部分客户类都没有任何关系,这样一来,导致测试工作量也会有所增加,需要进行大量不必要的重新验证和重新发行,费时费力。
CRP更多是告诉我们没有紧密联系的类不应该放在一个组件中,在一个组件中应该只包含那些需要一起被重用的类。
共同封闭原则(The Common Closure Principle, CCP)
The classes in a component should be closed together against the same kinds of changes. A change that affects a component affects all the classes in that component and no other components.
一个组件中的所有类对于同一种类型的变化应该是共同封闭的。一个变化若对一个组件产生影响,则将影响该组件中所有的类,而对其他组件不造成影响。
这个原则实际上就是组件的单一职责原则(SRP),也就是说一个组件不应该包含多个引起它发生改变的原因。在大多数应用中,可维护性比可重用性更重要。如果一个应用中的代码需要发生修改,尽量让这种修改都集中在一个组件中,而不是分散在多个组件中,例如更换数据库,只需要修改或更换与数据库操作有关的组件;更换视图层的界面,只需修改或更换新的界面组件。如果所有的更改集中在一个单一的组件中,我们只需重新发布那一个组件即可,这个组件通常也不会很巨大,易于维护和重用。如果将这些更改分散在多个组件中,将增加软件发布、验证和维护工作量。
实施CCP要求我们识别出那些关系很紧密的类,尽量将这些类封装到同一个组件中,避免后期需要修改多个组件。
CCP要求我们把对某一类型改变(例如更换数据库)敏感的类组织到同一个组件中,当需求中一个变更到来时,可以将变化限制在最少数量的组件中。
总结
在考虑如何将类组织到组件中时,需要充分考虑组件的可复用性和可维护性,一起被重用的类尽量放到一个组件中,一起受影响需修改的类尽量放到一个组件中。我们还要注意,随着项目的开展,组件的组成可能会随着时间而演化,需要相应作一些调整。我们在进行初始设计时需要尽量考虑全面,降低组件的修改工作量!
【作者:刘伟 http://blog.csdn.net/lovelion】