第十三章 组件聚合

哪些类应该归属到哪些模块?这是很重要的决定,需要有良好的软件工程原则的指导。不幸的是,多年来,这一决定几乎完全基于上下文而以临时方式作出。

这一章,我们将探讨组件聚合的三个原则:

  • REP: The Reuse/Release Equivalence Principle
  • CCP: The Common Closure Principle
  • CRP: The Common Reuse Principle

复用/发布等效原则(REP)

细粒度的重用是细粒度的发布

近十年来出现了很多模块管理工具,如Maven,Leiningen和RVM。这些工具的兴起很重要的原因是因为在这段时间,超大数量的可重用组件或组件库的创建。我们现在是生活在软件重用的年代-实现了面向对象模型中最古老的一个承诺。

复用/发布等效原则(REP)至少字面上看起来很明显,人们想重用软件组件的话,这些组件应当有发布的历史记录,每个记录都有发布号。

简单地想是因为没有发布号,没法保证所有的可重用组件是相互兼容的,而且有发布号可以让软件开发者了解新的发布版本的到来及其带来的变化特性。

当开发者得到新版本的提示,基于新版本的变化考虑而继续使用旧版本,这并不罕见。因此,发布过程应当生成适当的通知和发布文档,以便用户可以决定是否使用和合适去集成新的发布版本。

从软件设计和架构的观点来看,这原则意味着类和模块必须属于一个聚合组,形成组件。组件不该简单理解为保护随机类和模块,而是这些模块必须共有架构主旨或目的。

当然这很明显,但换个角度看这件事就不能不是很明显了。类和模块被组成模块应当也一起发布。他们共有同样的版本号和同样的发布历史,包含在同样的发布文档中,对于作者和用户都很有意义。

这是个薄弱的建议:说它有某种意义只是在空中摆动双手试图很权威发声。着建议很薄弱是因为很难准确的解释把这些类和模块组成单个组件的方法,这原则本身是重要的是因为很容易发现违反的现象-因为说不通。如果违反了REP,你的用户会知道的,并对你的架构技巧没好印象。

这个原则的薄弱更需要由下面两个原则来补充强度。确实CCP和CRP强定义了这个原则,但是是从负面意义的。

共同封闭原则(CCP)

集合成模块的这些类的发生变化应当为同样的原因,并且改变次数相同,对于这些类改变不同次数为了不同原因的,分离开成不同组件。

这是单一职责原则(SRP)对组件的重新描述。类似SRP描述那样,一个类不该包含多个使它变化的原因,所以共同封闭原则(CCP)描述一个组件不该包含多个使它变化的原因。

对大多数应用,可维护性比可重用性更加重要。如果应用里的代码要变,我们希望都在同一个组件变化,而不是分散到多个组件里。如果这些变化仅限于一个单一组件,我们只要重新部署这一个组件。其他不依赖这个变化组件的也不需要重新验证和重新部署。

CCP建议我们将所有类里可能由于相同原因变化的都集合到同一个地方。如果两个雷时关联紧密,无论是物理上还是概念上,总是一起变化,那么他们应当属于同一个组件。这个办法最小化相关的发布,重新验证,重新部署软件的工作量。

这个原则和开闭原则(OCP)关系密切。确实,CCP所指的“封闭”和OCP里的很像。OCP描述类应当对修改是关闭的对扩展是开放的。因为百分百的封闭不现实,封闭应该有点取舍。我们设计类时,这些类对我们期待或经验上最相同的变化种类是封闭的。

CCP强调了集合到同个组件的这些类对同样类型的变化是封闭的。因此,当需求的变化来了,这变化很大程度上把变化的组件的数量降到最低。

和单一职责原则(SRP)的相似性

之前描述,CCP是SRP的组件形式描述。SRP告诉我们分离为不同原因改变的方法到不同的类。CCP告诉我们分离为不同原因改变的类到不同的组件。两个原则可描述为:

集合成的这些东西的发生变化应当为同样的原因,并且改变次数相同,分离开这些对于为了不同原因,改变不同次数的东西。

共同重用原则(CRP)

别迫使用户依赖他们不需要的组件。

共同重用原则(CRP)是另一个帮助我们决定哪些类和模块应该被放在一个组件里的原则。它描述了倾向于被重用的类和模块属于一个组件。

类很少被孤立地重用。更多时候,可重用类和可重用抽象的其他类相互协作。CRP描述这些类属于同一个组件,这个组件我们预期是相互有大量依赖的。

一个简单的例子是容器类和它相关的迭代器。这些类是可重用因为他们彼此紧密耦合,他们应该在相同组件。

但是CRP不止告诉我们那些类应该放在同一个组件,它也告诉我们那些类不应该放在同一个组件。当一个组件用了另一个,这两个组件建立了依赖,可能用的组件只用了被用组件里的一个类,但这没有削弱依赖强度,用的组件仍然依赖被用的组件。

由于依赖关系,每次被用的组件改变了,用它的组件很需要应对这个改变。即使用的组件无需改变,它仍然很可能需要重新编译,重新验证,重新部署。即使用的组件并不关心被用的组件发生的改变,也可能这样。

因此当我们依赖一个组件时,我们想保证我们依赖了组件里的每一个类。换个角度,我们想确保我们放进同一个组件里的类不可分离,即不可能在同个组件里存在一个没有依赖其他类的类。否则,我们得重新部署比我们需要的更多的组件,浪费宝贵的精力。

因此CRP告诉我们哪些类应该放在一起,而哪些不应该。CRP描述彼此关联不紧密的类不该放到同一个组件里。

和接口隔离原则(ISP)的关联

CRP是ISP的一般化版本,ISP建议我们在类中别依赖用不到的方法,CRP建议我们在组件中别依赖用不到的类。

两者得关键可简化如下:

别依赖你用不到的东西。

组件聚合张力图

你可能已经发现了这三个聚合原则有相互排斥的倾向。REP和CCP是包含型(inclusive)的原则:两者有让组件更庞大的趋势。CRP是排外型(exclusive)的原则,是得组件更加小。三个原则的平衡是好的架构师应该去解决的。

图13.1是张力图,展示了三个聚合原则之间的联系。从顶点逆时针,每条边描述了放弃该原则的代价。

图13.1 聚合原则张力图

只关注REP和CRP的架构师可能导致一个简单地变化是得非常多的组件受影响。相反,架构师只关注CCP和REP可能导致非常多不需要的发布生成。

好的架构师应当在三角张力中找到一点满足开发团队的当下痛点,但同时要意识到痛点会随时间变化。比如在开发项目早期,CCP比REP更加重要,因为开发效率比重用更重要。
一般来说,项目开始倾向于张力图右侧,不考虑重用。随着项目进行,和其他项目脱胎出来,项目将滑向张力图的左侧。这意味着项目的组件结构会随着时间和成熟度变化。它与项目开发和使用的方式有关,再与项目的实际内容有关。

小结

过去,我们的聚合观点比REP,CCP,CRP的含义更简单。我们曾经简单地想聚合就是一个模块有且仅表现成一个函数。然而,这三个组件聚合原则描述的聚合更加复杂性和多变性。在选择类的分组成组件上,我们必须考虑去平衡可重用性和开发效率的拉扯力。平衡这个拉扯力对于项目的意义非常必要。而且这平衡总是动态的。也就是今天可能合适,明年就不合适了。因此,随着项目的重点从可开发效率转向可重用性,组件的组成可能会随着时间的推移而变化和演变。

results matching ""

    No results matching ""