原文:http://blog.csdn.net/drzhouweiming/archive/2007/04/28/1589436.aspx
相关文章链接:
模块分解原理探索
在Robert C.Martin著的《敏捷软件开发-原则、模式与实践》一书中,提出了许多的设计原则,这里想对其中的一条稳定依赖原理(中文版P232页)进行一些探索。
稳定依赖原则讲的是“朝着稳定的方向进行依赖”,在前面提到的书中认为稳定性和更改所需的工作量有关。
这里有两个术语值得先探讨一番,一个是“依赖”,另一个是“稳定性”,什么叫依赖,什么叫稳定性,书中并没有给出确切的定义。依赖这次词在业界有多种理解,如编译依赖,执行依赖,接口依赖等。而稳定性这个词如果不进行确切的定义的话则是一个更难以理解清楚的词汇,是需求稳定、代码稳定还是什么稳定呢?
下面就来对软件架构设计中的接口关系设计和稳定性方面的原理进行一些探索。
在软件架构设计中,如何设计各个模块间的接口关系是一个重要问题,对于给定的两个模块A和模块B,到底应该是模块A调用模块B的接口还是应该B调用A呢?现在绝大多数程序员都是凭经验来设计它们的接口调用(依赖)关系,而初学者则通常是根据执行依赖关系来设计接口依赖关系,将接口依赖关系和执行依赖关系设计成一模一样,有经验的程序员会发现有许多情况接口依赖关系不能和执行依赖关系一样,业界还有一些设计模式就是将模块的接口依赖关系倒置过来的。
设计模式中将模块接口依赖关系倒置通常都是对应于一些特定情况,有没有普遍的原则能够用来判定什么情况下应该将接口依赖关系倒置呢?
要解决接口关系的设计问题,还是要先看一看下面的接口关系稳定原理,
接口关系稳定原理:应该让接口稳定性低的模块调用接口稳定性高的模块
首先要对这个原理里说道的两个术语进行一些解释,“接口稳定性”指的是接口本身在未来可能被修改的程度,不包括接口实现时的修改工作量。而“调用”则是指接口依赖。
接口的修改程度包括两个内容,一个是接口的修改工作量,另外一个是接口被修改概率有多大,修改程度等于两者的乘积。
在这个原理中,其实和前面的“朝着稳定的方向进行依赖”有很大的相似之处,但是在具体的细节上则有着明显的区别,如果用在实际情况中,可能会得出完全不同的结果。
例如一个软件中的某个模块A要使用排序法模块B,排序算法将来随着需求的变化可能会发生变化,例如由插入排序算法改成快速排序算法或归并排序算法,而且一改动时整个排序模块要完全重新实现,修改工作量很大。按照Robert C.Martin的稳定性是指修改的工作量的理解,排序算法模块是极不稳定的,那么应该由排序算法模块B去调用模块A。
但是在现实情况中,可能没有人会在一个排序算模块中去调用其他模块,为什么呢?因为排序算法模块虽然变化后的修改工作量很大,但是排序算法的接口可以设计成稳定的不用修改的,所以按照接口关系稳定原理来理解,仍然应该让模块A调用排序算法模块B,所以接口关系稳定原理和实际情况是一致的。再看看接口稳定关系原理和实践中的其他情况是否一致,下面以设计模式中的命令模式为例来分析一下。
命令模式中,命令的管理和具体命令的实现存在执行依赖关系,但是命令模式中,并没有让命令管理模块去调用具体的命令实现模块,而是让具体的命令实现模块去调用命令管理模块将自己注册进去,实现了接口依赖关系的倒置。
为什么命令模式中要进行接口依赖关系的倒置呢?这其实是由接口关系稳定原理决定的,命令管理模块是一个有着非常稳定接口的模块,而具体的命令实现模块,由于命令可以任意增加,存在着许多不稳定因素,按照接口关系稳定原理,让接口不稳定的模块调用接口稳定的模块,所以命令模式是符合接口关系稳定原理的。其实其他类似命令模式这种有接口依赖关系倒置的地方都是按照接口关系稳定原理来设计的。
下面再来根据接口关系稳定原理推导出一些有用的结论:
模块分层定理:符合接口关系稳定原理的系统是可分层的系统
在证明这个定理前,我们先看一下前面《敏捷软件开发》一书中提到的另外一个原则:“无环依赖原则”,无环依赖原则讲的是“包的依赖关系图中不允许存在环”。
其实这个无环依赖原则可以由接口关系稳定原理推导出来,假设一个符合接口关系稳定原理的系统中的各个模块之间的接口关系图中存在环,将环上的各个模块依次记为A1,A2,A3,…An,其中A1依赖于A2,A2依赖于A3,…,An依赖于A1。
按照接口关系稳定原理,A1依赖于A2表明A1的接口稳定性比A2低,同理A2比A3低,…,An-1比An低,这样A1的接口稳定性应该比An低,但是An又依赖于A1,表明A1的稳定性又高于An,这就矛盾。
因此可以得出结论,符合接口关系稳定原理的系统中不存在接口依赖关系环,而无环有向图是可以分层的图(无环有向图的分层算法参见《多任务下的数据结构算法》一书),这样定理就得到证明。
从模块分层定理可以看出,接口关系稳定原理和业界公认的软件系统是分层的系统是一致的。
定理:符合接口关系稳定原理的系统总修改工作量的期望值最小。
以前面说过的某模块A调用排训算法模块B为例,分别考虑A调B和B调A的修改工作量情况:
当模块
A
调用
B
1)如果A发生修改,那么修改的工作量是模块A的修改工作量,模块B由于没有调用模块A,不需要做任何修改。A需要修改
2)如果B发生修改,那么修改的工作量是模块B的修改工作量,模块B由于接口稳定,没有改动接口,模块A不需要做任何修改。B需要修改
当模块
B
调用
A
3)如果A发生修改,修改时如果改动了接口时,模块B也需要跟着修改。A,B都需要修改
4)如果B发生修改,模块B需要修改,模块A不需要修改。B需要修改
从上面可以看出,2)和4)的修改工作量是一样的,但3)比1)的修改工作量大,因此A调用B的修改工作量比B调用A的总修改工作量的期望值小。
当然上面为了描述起来更简单一些,第2)种情况种将模块B的接口稳定看成了接口完全不需要修改。但事实上接口稳定并不是一个绝对的概念,它是一个接口修改程度的概念,不妨以F(A)表示模块A接口的修改量,以X(A)表示模块A的修改量
那么可以得出
A调用B的总修改工作量的期望值 = X(A) + X(B) +F(B)
B调用A的总修改工作量的期望值 = X(A) + F(A) +X(B)
由于模块B比模块A稳定,因此F(B)小于F(A),所以A调用B的修改工作量要比B调用A的修改工作量小。
再考虑系统中有多个模块的情况,实际上只要有两个模块的调用关系和接口稳定依赖关系相反的话,那么可以把它们的接口依赖关系倒置过来,显然这两个模块和其他模块间的修改工作量没有发生改变,但由上面的分析知道这两个模块的修改工作量变小了。所以不符合接口关系稳定原理的总修改工作量一定会大于符合接口关系稳定原理的系统,换句话说,符合接口关系稳定原理的系统总修改工作量的期望值是最小的。
Trackback: http://tb.blog.csdn.net/TrackBack.aspx?PostId=1589436