模块化方法是一种软件开发方法,是把一个待开发的软件分解成若干小的简单的部分,是对复杂事物分而治之的经典原则1。涉及的主要问题是:模块设计的规则,系统如何分解成模块。每一模块都可独立开发、测试,最后再组装成整个软件。对一个规约进行分解,以得到模块系统结构的方法有数据结构设计法、功能分解法、数据流设计、面向对象的设计等。

简介模块化概念首先提出于产品设计领域,是在传统顺序化设计制造模式基础上发展起来的一种新的设计思想。人们将“产品”定义为具有某种功能,能够满足人们某种需求的东西。而所谓模块,就是将产品中的一些组成要素集合起来,划分成一个具有特定功能的“半自律性子系统”,这个子系统内部的结构要素之间联系紧密,而子系统与其他子系统之间关系相对独立。划分这个子系统,并且将多个子系统相互组合形成上级系统直至形成最终产品的过程被称为模块化。每个模块都具有一个特定的子功能,所有这样的模块按照某种规则组装起来形成一个整体系统。在整个系统中,模块是可组合、可分解、可更换的独立单元。模块化方法就是将一个复杂问题分解成多个独立的、相互关联的模块,再通过分别独立地实现各模块的功能,最终将所有模块组合起来以处理大型、复杂问题的一种途径2。

模块化方法是一种软件开发方法,把一个待开发的软件分解成若干小的简单的部分,称为模块。每一个模块都独立地开发、测试,最后再组装出整个软件。这种开发方法是对待复杂事物的“分而治之”的一般原则在软件开发领域的具体体现。模块化开发方法涉及的主要问题是:模块设计的规则,系统如何分解成模块。模块化方法在产品设计中的目的在于:

力求用尽量少的模块实现尽量多的产品,在满足产品需求的情况下,提高模块在不同组合中的重用性,以减少重复投入,并且好的重用性带来单一模块批量生产的边际效益,进一步降低成本。

用尽可能小的代价实现产品性能和功能的更新,合理地设计模块外部特性,提高等位模块之间的互换性。这样不仅可以将变更影响控制在一个最小范围内,而且在产品进入维护保养阶段的生命周期后,也可以实现故障模块的快速替代。

原则单一职责原则。即一个模块不要存在多于一个导致模块变更的因素。也就是说,一个模块只负责一项功能,或者即使一个模块具有多个功能,但必不存在对其中一个功能的改变会影响想到其他功能的情况。

里氏替换原则。在面向对象型编程中,这一原则是表达:子类可以扩展父类的功能,但不能改变父类原有的功能。而对于一般产品的模块化设计而言,这一原则诉说的是模块与模块系列之间的关系,是解决模块通用性和互换性的指导。由于模块化设计是自顶向下的,在进行模块内部细节设计之前,人们已经定义好了上级系统的功能组合,规划好了上级系统的组成与结构。在系统中一个必要模块可以被与之相似的功能可以替用的其他模块所替换,而不必影响到系统结构,也不会要求系统中别的关联模块做出更改。那么这个模块和可替换它的其他模块被称为一个模块系列。一个系列下的所有模块必然有一个或多个共同遵守的规则,虽然各模块都具有各自与众不同的特殊功能,但一定是都完全满足这个模块系列在系统中的基本功能。比如说一个计算机系统需要一台彩色显示器,我们可以提供一台带有音箱的显示器,但不能提供一台单色显示器。

依赖倒置原则。高层模块不应该受制于低层模块。一个简单的系统可以是多个模块的简单一维组合。但随着系统复杂程度的上升,模块的层级结构不可避免。如果模块A的功能实现依赖于模块B的功能,那么B就是A的低层模块。

接口隔离原则。一个模块对另一个模块的依赖应建立在最小接口之上。根据上一条依赖倒置原则,我们创造出模块I来起到模块A与模块B或者C的接口作用。随着低层模块的不断增多,模块I为了适配每一个不同的低层模块,其所携带的不同接口的兼容性设计也就越来越多。虽然I的兼容性越好,意味着需要引入的新模块数量就减少,仿佛实现了“极可能少的模块实现尽可能多的产品”这一目标,但在现实中,接口臃肿的模块I在参与到系统组合中的时候,无论有用与否,所有的接口都在耗费着系统的资源,无论成本上还是效率上考虑,都不是合理的安排。这种情况下,应当将臃肿的模块I拆分设计成一系列相对独立的接口模块J,K…。这个系列中的每一个模块都只适用于所需要它们的场合。在现实中,如何定义模块的接口,每个模块应具备兼容接口的数量,是需要根据组合的复杂程度、变更灵活性上面进行辩证地思考。

迪米特法则。在面向对象型编程中,这一原则是这样定义的:一个对象应该对其他对象保持最少的了解。在一般性设计中,这是指要尽量降低构成要素之间的耦合。要素只与同模块内的其他要素相关联,没有跨模块联系。任何需要从一个模块传递到另一个模块去的影响都将通过模块与模块之间的标准接口实现。

开闭原则。是指当需求发生改变,需要对功能模块进行修改,在原有系统中其他模块还在发挥作用的情况下,不要对原有模块进行修改,因为这些修改何有可能带来难以预测的衍生问题,而应该为现有模块系列增添新的符合新需求的模块与原有模块进行互换,或者在系统中增加新的模块满足新的功能。

模块与设计规则模块是执行一个特殊任务或实现一个特殊的抽象数据类型的一组例程和数据结构。模块通常由两部分组成。接口:列出可由其它模块或例程访问的常数、数据类型、变量、函数等;实现:私有量(只能由本模块自己使用的)及实际实现本模块的源程序代码。模块的接口部分刻画了各个模块是如何耦合的,是其它模块的设计者和使用者所需要知道的。而实现部分是各模块的内部事务,其它模块并不需要知道。这也体现了对待复杂事物的另一原则,抽象原则,即把非本质的性质隐藏起来,只突出那些本质的性质,以减轻人们思考和注意的负担。模块化澄清和规范化了软件中各部分间的界面。如此就便利了成组的软件设计人员工作,也促使了更可靠的软件设计实践。在把系统分解成模块时,应该遵循以下的规则:

①得到最高的模块内聚,也就是在一个模块内部的元素最大程度地关联。只实现一种功能的模块是具有最高内聚的,具有三种以上功能的模块则是低内聚的。②最低的耦合,也就是不同模块之间的关系尽可能弱。③模块大小适度。④ 模块调用链的深度不可过多。⑤接口干净,信息隐蔽。⑥尽可能地复用已有模块。

如何对一个规约进行分解,以得到模块化的系统结构。已经有一些基于设计规则的方法。

数据结构设计方法。最著名的是Jackson结构化设计。它从画出所有输入、输出数据的逻辑结构图开始, 最后得到程序结构图,反映了系统结构。可能还需要继续对其中模块求精,得到更低级的模块,但是基本程序结构是不变的。

功能分解。步骤是:陈述出功能意图(即要解决的问题),进行功能求精(即划分层次),连接求精了的功能,进行检查,再求精,再检查,直至得到满意的解决为止。这种方法,也称为结构化设计、层次化分解、模块分解、功能分解。

数据流设计。步骤是:把问题分解成由动作图(也称为进程)和数据图(也称为流)组成的数据流图,还有存放待处理的静态信息的存储元素。然后从数据流图中找出中心进程 ,以它为根,把数据流图转换为树形结构。按照功能分解形式来分解进程,即把模块分为三类(输入、变换、输出),进行进程求精,得到 PDL 语言表示。最后把 PDL 变成某种程序语言。

面向对象的设计。这一方法要求标识出对象及其属性、每个对象所需要的操作。把数据及函数封装在一起,以形成类。并建立其间相互可见的关系,即许可的调用与被调用关系,形成每个类的界面。最后是实现每个类。

特点易设计:较大的复杂问题分解为若干较小的简单问题,使我们可以从抽象的模块功 能角度而非具体的实现角度去理解软件系统,从而整个系统的结构非常清晰、容易 理解,设计人员在设计之初可以更加关注系统的顶层逻辑而非底层细节。

易实现:模块化设计适合团队开发,因为每个团队成员不需要了解系统全貌,只需 关注所分配的小任务。另外团队可以灵活地增加人手,新人只需直接接手某个模块, 不会影响系统其他模块的开发。

易测试:每个模块不但可以独立开发,也可以独立测试,最后组装时再进行联合测 试。

易维护:如果需要修改系统或者扩展系统功能,只需针对特定模块进行修改或者添 加新模块。

可重用:很多模块的代码都可以不加修改地用于其他程序的开发。

本词条内容贡献者为:

徐恒山 - 讲师 - 西北农林科技大学