组件设计原则之概念篇(二)

               

       前三个组件设计原则关注组件的内聚,从本文开始,接下来将要介绍的三个原则更多关注组件间的耦合,其难度比前三个原则要大,我将结合一些示例进行讲解,主要参考资料仍然是Robert C.Martin的《敏捷软件开发:原则、模式与实践》(Agile Software Development: Principles, Patterns, and Practices)一书中的“Principles of Package and Component Design”一章。在讲解组件设计原则时,组件的涵义与包的涵义相似,都表示包含多个元素的组织结构。由于下面要介绍的无环依赖原则(ADP)稳定依赖原则(SDP)稳定抽象原则(SAP)相对较为复杂,因此,我决定分开进行讲解,尽量让每一个原则的讲解都能够全面而又深入,如有问题,欢迎大家多多与我交流!吐舌头

 

组件耦合性原则:稳定性 (Principles of Component Coupling: Stability)

无环依赖原则(The Acyclic Dependencies Principle, ADP)

Allow no cycles in the component dependency graph.
在组件的依赖关系图中不允许存在环。

 

环形依赖和无环依赖

       ADP其实很容易理解,就是说在组件与组件之间不应该存在环形依赖关系。对于一些规模较大的项目,难免会包含多个组件,不同的组件可能会是由不同的开发团队所开发的,当一个组件发生改变时,所有依赖于它的其他组件也将需要重新集成和测试,如果在依赖关系中存在环,将导致循环依赖,使得一个组件修改之后的影响范围将变得非常大,致使该组件难以升级和重新发布。

       首先我们来看一个无环依赖的例子,如图1所示。在图1中,组件Component3依赖于组件Component4和Component5,当需要修改Component3时,只需要重新链接Component4和Component5即可,对Component2没有任何影响;此外,修改Component4或Component5会导致Component3和Component1受到影响(沿着逆向的依赖关系即可寻找到所有受影响的组件),开发Component3和Component1这两个组件的开发人员可以决定是否需要和Component4或Component5的新版本集成以及何时集成。

 

图1 无环依赖示意图

       如图1所示的依赖关系图是一个有向无环图 (Directed Acyclic Graph, DAG)。在发布整个系统时,可以按照自底向上的次序,首先编译、测试和发布Component4和Component5,然后是Component2和Component3,最后是Component1,这个开发和设计流程非常清晰,操作起来很容易。

       如果我们设计不当,则会造成组件之间出现环形依赖,如图2所示:

图2 环形依赖示意图

        图2所示的这种循环依赖将导致系统产生很多问题:在无环系统中,如果需要发布一个组件Component3的新版本,只需要重新链接Component4和Component5,因为Component4和Component5没有再依赖别的组件,所需时间和工作量都较小。但是在一个具有环形依赖的系统中,Component5又将依赖Component1,Component1依赖Component2,这导致整个系统所有组件全部需要重新编译和链接,使得Component3的发布变得非常麻烦,而且每一次发布的时候,重新构建(集成)整个系统所花费的成本将急剧升高,为了一个组件的发布就将做一次完整的构建,这会严重影响系统的开发进度,难过

      此外,系统的集成和测试也变得越来越难以进行,难过,系统耦合度增加,修改和扩展都很麻烦,在有些更为复杂的项目中,如果存在大量的环形依赖,编译时间会随着模块数量成几何级数增长,毫无疑问,那绝对是无法忍受的!    

       而且在这种环形依赖系统中,组件的构建顺序也是一个很大的问题,由于组件之间存在循环依赖,那么哪一个组件应该第一个被构建呢?在编译一个组件时,它所依赖的组件从何而来,这居然变成了一个“先有鸡还是先有蛋”的问题,在有些编程语言中,此问题无解!快哭了

 

环形依赖的消除

       Bob在ASD一书中提到了两种将组件间的环形依赖改造为DAG的方法,下面详细介绍一下这两种方法,里面也融合了我自己的一些见解,希望对大家有所帮助。

       1. Apply the Dependency-Inversion Principle (DIP):应用依赖倒转原则

       我就拿ASD书中的例子来说明一下如何实施,如图3所示:

  图3 使用DIP消除依赖环

        如图3所示,假设在某系统中,如果我们可以通过消除组件MyDialogs和MyApplication之间的依赖关系就可以消除环形依赖,通过分析,我们得知在组件MyDialogs中的类X依赖组件MyApplication中的类Y,此时,可以作出如下重构:

        (1) 在组件MyDialogs中增加一个接口,如图3所示的X Server,类X依赖该接口;

        (2) 将类Y作为接口X Server的实现类。

        经过如上两步,依赖关系由MyDialogs依赖MyApplication变成了MyApplication依赖MyDialogs,编译次序为先编译MyDialogs,后续再编译MyApplication,由于X针对抽象的接口编程,使得系统的可扩展性变得更好。

        此重构将组件之间的具体依赖关系转换为反向的抽象依赖关系,从而消除环形依赖。

 

        除此之外,如果涉及到的类太多,不希望在原有组件中增加过多的接口,还可以增加一个新的组件,在这个新组件中包含一系列接口,原有组件针对这个包含多个接口的组件编程,如图4所示:

图4 增加抽象组件消除依赖环

      在图4中,我们增加了一个新的抽象组件AbstractComponent,Component5不再直接依赖Component1,在AbstractComponent中包含了一组接口,这些接口的实现类在Component1中,Component5针对抽象接口编程,因此,Component5只需依赖AbstractComponent即可。此时,编译次序为:AbstractComponent --> Component5, Component4 --> Component3, Component2 --> Component1,可见,新的抽象组件的引入完全消除了环形依赖,而且由于AbstractComponent包含了大量的接口,让系统具有更好的可扩展性,如果提供一组新的实现AbstractComponent中接口的具体类,原有系统无须做太多的修改,符合开闭原则。

 

       2. Create a new component that both of the components depend on:创建一个共同依赖的新组件

       还是以图2为例,我们可以创建一个Component1和Component5都依赖的新组件,例如将原来Component1中Component5所依赖的那些类抽取出来,封装到新的组件,例如NewComponent中,此时,Component1和Component5都将依赖NewComponent,NewComponent也将成为第一个被编译的组件,如图5所示:

图5 增加新组件消除依赖环

       在实际开发中,随着项目规模的扩大和应用程序的增加,组件之间的依赖关系会发生改变(组件中类之间的关系也会进一步清晰),我们需要随时对组件中的环形结构进行监控。如果出现了环形结构,就必须要使用某种方法来将其消除。这可能会需要创建一些新的组件,导致依赖链增长,依赖关系变得更加复杂,但为了消除依赖环,适当增加一些新的类或者新的组件是必须的,因为依赖环的存在是百害无一利,微笑

 

       简而言之,ADP就是在组件的依赖关系图中不能存在环,如果存在,我们就应该想办法来消除环形依赖!

【作者:刘伟 http://blog.csdn.net/lovelion

           

猜你喜欢

转载自blog.csdn.net/qq_44906668/article/details/89787940