敏捷软件开发 – 包和组件的设计原则

  1. 在向组件中分配类时应该依据什么原则?
  2. 应该使用什么设计原则来管理组件之间的关系?
  3. 组件的设计应该先于类呢(自顶向下),还是类的设计应该先于组件(自底向上)?
  4. 组件实体以什么方式存在呢?在C#中如何存在?在开发环境中如何存在?
  5. 组件创建好后,我们应当将它们用于何种目的?

  前3个原则-包的内聚性原则-是用来指导如何把类划分到包中的。后3个原则用来管理包之间的耦合,有助于我们处理包之间的相互关系。最后两个原则同时还给出了一组依赖管理度量,可以使得开发者度量、刻画设计的依赖结构。

组件的内聚性原则:粒度

  组件的内聚性原则可以帮助开发者决定如何把类划分到组件中。这些原则基于这样的事实:至少已经存在一些类,并且它们之间的互相关系也已经确定。因此,这些原则是根据“自底向上”的观点对类进行划分的。

重用-发布等价原则 Reuse-Release Equivalence Principle, REP

  一个组件的重用粒度(granule of reuse)可以和发布粒度(granule of release)一样大。我们所重用的任何东西都必须同时被发布和跟踪。简单的编写一个类,然后声称它是可重用的做法是不现实的。只有在建立一个跟踪系统,为潜在的使用者提供所需要的变更通知、安全性以及支持后,重用才有可能。

  REP带给我们了关于如何把设计划分到组件中的第一个提示。由于重用性必须是基于组件的,所以可重用的组件必须包含可重用的类。因此,至少某些组件应该由一组可重用的类组成。
我们必须从潜在的重用者的角度去考虑组件的内容。如果一个组件的软件是用来重用的,那么它就不能再包含不是为了重用目的而设计的软件。一个组件中的类要么都是可重用的,要么都不是可重用的。

共同重用原则 Common-Resue Principle, CRP

  一个组件中的所有类应该都是共同重用的。如果重用了组件中的一个类,那么就要重用组件中的所有类。

  这个原则可以帮助我们决定应该把哪些类放进同一个组件中。CRP规定了趋向于共同重用的类应该属于同一个组件。

  类很少会孤立的重用。一般来说,可重用的类需要与作为该可重用抽象一部分的其他类协作。CRP规定了这些类应该属于同一个组件。在这样的组件中,我们会看到类之间有很多的相互依赖。

  但是,CRP告诉我们的不仅仅是什么类应该共同放入一个组件中。它还告诉我们什么类不应该放入同一个组件中。当一个组件使用了另一个组件时,它们之间会存在一个依赖关系。也许一个组件仅仅使用了另外一个组件中的一个类。然而,那根本不会削弱这两个组件之间的依赖关系。

  CRP规定相互之间没有紧密联系的类不应该放在同一组件中。

共同封闭原则 Common-Closure Principle, CCP

  组件中的所有类对于同一种性质的变化应该是共同封闭的。一个变化若对一个封闭的组件产生影响,则将对该组件中的所有类产生影响,而对于其他组件则不造成任何影响。

  这是单一职责原则(SRP)对于组件的重新定义。正如SRP规定的一个类不应该包含多个引起变化的原因那样,CCP规定了一个组件不应该包含多个引起变化的原因。

  在大多数的应用程序中,可维护性的重要性是超过可重用性的。如果一个应用中的代码必须更改,那么我们宁愿更改集中在一个组件中,而不是分布在多个组件中。如果更改集中在一个单一的组件中,那么我们仅仅需要重新部署那个更改了的组件。不依赖与那个更改了的组件的其他组件则不需要重新验证或者重新部署。

  CCP鼓励我们把可能由于同样的原因而更改的所有类共同聚集在同一个地方。如果两个类之间有非常紧密的绑定关系,不管是物理上的还是概念上的,那么它们总是会一同进行变化,因而它们应该属于同一个组件中。这样做会减少软件的发布、重新验证、重新发行的工作量。

组件的耦合性原则:稳定性

无环依赖原则 Acyclic-Dependencies Principle, ADP

  在组件的依赖关系图中不允许存在环。

  通过把开发环境划分为可发布的组件,这些组件可以作为工作单元由一个开发人员或者一个开发团队负责完成。当开发人员完成一个组件时,就把它发布给其他开发人员使用。它们赋予该组件一个版本号并把它移到一个供其他开发人员使用的目录中。接着,他们可以在自己的私有区域中继续修改他们的组件。其他所有人都使用那个已经发布的版本。

  当制作了一个组件的新版本时,其他开发团队可以决定是否马上采用这个新的版本。如果决定不采用,则他们完全可以继续使用老的版本。一旦觉得自己准备就绪,就可以开始使用新的版本。

  因此,所有的开发团队都不会受其他开发团队的支配。对一个组件作的更改不必立即反应到其他开发团队中。每个开发团队独立决定何时采用目前所使用的组件的新版本。此外,集成是以小规模增量的方式进行的。这样,就不会再发生所有的开发人员必须集合到一起把他们做的每一件工作集成起来的情况。

  这是一个非常简单、合理的过程,并被广泛使用。不过,要使其能够工作,就必须要多组件的依赖关系结构进行管理。组件的依赖关系中不能有环。

有向无环图(directed acyclic graph, DAG)

  当发布整个系统时,是自底向上进行的。首先编译、测试以及发布Windows组件。接着是MessageWindow和MyDialogs。在它们之后是Task,然后是TaskWindows和Database。接着是MyTasks,最后是MyApplication。这个过程非常清除并且易于处理,我们知道如何去构建系统,因此我们理解系统各个部分之间的依赖关系。

带环的组件图

自顶向下设计与自底向上设计

  不能在没有代码的情况下自顶向下地设计组件的结构。组件的结构是随着系统的增长、变化而逐步演化的。

  组件的依赖关系结构是应用程序可构建性的映射图。这就是为什么无法在项目开始时完全设计出它们的原因。同样也说明了它们为何不是严格的功能分解。随着实现和设计初期积累的类越来越多,对依赖关系进行管理,避免项目开发中出现次晨综合症的需要就不断增长。此外,我们也想尽可能地保持更改的局部化,所以我们开始关注SRP和CCP,并把可能会一起变化的类放在一起。

  随着应用程序的不断增长,我们开始关注创建可重用的元素,于是,就开始使用CRP来指导组件的组合。最后,当环出现时,就会使用ADP,从而组件的依赖关系图就会出现抖动以及增长,原因更多地是因为依赖结构而非功能。

  如果在设计任何类之前试图去设计组件的依赖关系结构,那么很可能会遭受惨败。我们对于共同封闭还没有多少了解,也还没有察觉到任何可重用的元素,从而几乎当然会创建依赖环的组件。所以,组件的依赖关系结构是和系统的逻辑设计一起增长和演化的。

  不过,无需花费太长的时间,组件结构就会趋于稳定从而可以支持多团队开发。此后,团队就可以关注于自己的组件。团队之间的交流就可以局限于组件的边界处。这就使得多团队可以在最小开销的情况下,同时工作在同一个项目上。

  但是,请记住,这个稳定的组件结构将会随着开发的进展持续地抖动和变化。这会导致组件团队之间无法完全的隔离。在组件之间的依赖关系出现问题时,这些团队将不得不工作在一起。

稳定依赖原则 Stable-Dependencies Principle, SIP

  设计不能是完全静态的。要使设计可维护,某种程度的易变性是必要的。我们通过遵循共同封闭原则(CCP)来达到这个目标。使用这个原则,可以创建对某些变化类型敏感的组件。这些组件设计成可变的。我们期望它变化。

  对于任何组件而言,如果期望它是可变的,就不应该让一个难以更改的组件依赖于它!否则,可变的组件同样也会难以更改。

  你设计了一个易于更改的组件,其他人只要创建一个对它的依赖就可以使它变得难以更改,这就是软件的反常特性。没有改变你的模块中的任何一行代码,可是它突然之间就变得难以更改了。通过遵循SDP,我们可以确保那些打算易于更改的模块不会被那些比它们难以更改的模块所依赖。

稳定性

  使软件组件难以更改的因素有很多:它的规模、复杂性、清晰程度等等、我们会忽略所有这些因素而关注某个不同的东西。要是一个组件难以改变,一个肯定可行的方法是让许多其他的组件依赖于它。具有很多输入依赖关系的组件是非常稳定的,因为要使所有依赖于它的组件能够相容于对它做的所有更改,往往需要非常大的工作量。

  稳定组件X。有3个组件依赖于它;因此,就有3个合理的理由不去更改它。X对这3个组件负有责任。另外,X不依赖于任何组件,因此所有的外部影响都不会使其改变。X是无依赖性的。

  非常不稳定的组件Y。没有任何其他的组件依赖于Y;Y是不承担任何责任。此外,Y依赖于3个组件,所以它具有3个外部更改元。Y是依赖性的。

稳定性度量

  一种方法是计算进、出该组件的依赖关系的数目。可以使用这些数值来计算该组件的位置稳定性(positional stability)

  (Ca)输入耦合度(afferent coupling):处于该组件的外部。并依赖于该组件内的类的数目。

  (Ce)输出耦合度(efferent coupling):处于该组件的内部。并依赖于该组件外的类的数目。

  (不稳定性I) = Ce/(Ca+Ce)

  度量的取值范围是 [0,1]。=0表示该组件具有最大的稳定性。=1表示该组件具有最大的不稳定性。

 

  Pc外部有3个类依赖于Pc内部的类。所以,Ca=3。此外,Pc外部有一个类被Pc内的类依赖。所以Ce=1。I =1/4。

  SDP规定一个组件的I 度量值应该大于它所依赖的组件的I 度量值。也就是说,I 度量值应该顺着依赖的方向减少。

多样的组件稳定性

  如果一个系统中所有的组件都是最大程度稳定的,那么该系统就是不能改变的。这不是我们希望的情形。事实上,我们希望所设计出来的组件结构中,一些组件是不稳定的而另外一些组件是稳定的。

  理想的组件配置。

 

  违反SDP的配置。对Flexible的更改会迫使我们去处理该更改对Stable机器所有依赖者的影响。

高层设计的位置

  如果把高层设计放进稳定的组件中,那么体现高层设计的编码就会难以更改,这回事设计变得不灵活。怎样才能让一个具有最高稳定性(I =0)的组件足够的灵活,可以经受得住变化呢?在OCP中可以找到答案。OCP原则告诉我们,那些足够灵活可以无需修改即可扩展的类是存在的,并且是所希望的。哪种类符合OCP原则呢?抽象类

稳定抽象原则 Stable-Abstractions Principle, SAP

  组件的抽象程度应该与其稳定程度一致。

  该原则把组件的稳定性和抽象性联系起来。一方面,它规定,一个稳定的组件应该也是抽象的,这样它的稳定性就不会使其无法扩展。另一方面,它规定,一个不稳定的组件应该是具体的,因为它的不稳定性使得其内部的具体代码易于修改。

  因此,如果一个组件是稳定的,那么它应该也要包含一些抽象类,这样就可以对它进行扩展。可扩展的稳定组件是灵活的,并且不会过分限制设计。

  SAP和SDP结合在一起形成了针对组件的DIP原则。SDP规定依赖应该朝着稳定的方向进行,而SAP则规定稳定性意味着抽象性。因此,依赖应该朝着抽象的方向进行。  

  然而,DIP是一个处理类的原则。类没有灰度(the shades of grey)的概念。一个类要么是抽象的,要么不是。SDP和SAP的结合是处理组件的,并且允许一个组件是部分抽象、部分稳定的。

 

 

摘录自:[美]RobertC.Martin、MicahMartin著,邓辉、孙鸣译 敏捷软件开发原则、模式与实践(C#版修订版) [M]、人民邮电出版社,2013、308-322、

posted @ 2016-09-14 14:39  guqiangjs  阅读(1536)  评论(0)    收藏  举报