本篇博客意在对比软件工程领域的结构化方法与面向对象化方法,在开始对比之前我们要先知道这些方法出现的背景,并对这两种方法有一个大致的了解。下面先让我们来一起探究一下它们出现的背景。
1. 背景
对于软件工程领域而言,它的终极目标是:有效地得到一个运行的系统及其技术支持文档,同时该系统需要满足有关的质量要求。
这样的系统就是软件开发方法最终需要达到的目标。在达到这个最终目标的过程中,我们需要实施一系列的软件活动,包括设计,约定接口,制定编程规范等。软件的需求越多,体积越庞大,我们实施的软件活动本身的复杂性也就越高。为了能够有效地组织起软件生存周期中这些活动的实施,我们需要选择相应的组织框架,进而形成特定的项目生存周期的过程。这也就是结构化方法和面向对象化方法本身的意义所在,它们可以帮助我们更好地规划与管理软件工程中具体活动的实施。
很多人都觉得自己不曾在软件工程中使用过结构化的或面向对象化的方法,博主猜测他们这么体会的主要原因可能是他们并没有实施过完整的软件构建流程,而仅仅是将软件当作了一段程序来写。
写一段程序和构建一个软件是有很大不同的,拿ACM竞赛中的比赛程序举例,这些程序都有固定的往往不带提示信息的输入与输出,程序写得好与不好主要取决于算法的性能,有些时候也会参考代码的风格。但它们相比软件很大的不同之处在于:程序不需要考虑用户输入,也不需要考虑用户体验。但是软件工程活动构建的产物——由需求而非输入输出定义的软件需要这些额外的东西。
而构建一个软件则需要细致的计划,编写,测试等,下面就来一起看一下结构化方法吧。
2. 结构化方法
一般来说软件的生命周期可以被划分为:软件计划,软件分析,软件设计,实际编码,软件测试(包括了黑盒,白盒,单元,回归测试等)及运行维护等阶段表示。结构化方法包含了其中的两个部分:软件工程结构化分析(structured analysis,SA) 以及 软件工程结构化设计(structured design,SD)。
2.1. 结构化分析
结构化分析是结构化方法的一部分,其主要目标是系统化地使用给定的信息,能够形成对问题的估算求解。
举个例子,对于软件工程中的需求分析环节,我们如何结构化分析呢?
- 首先我们要对需求陈述进行分析,解决其中可能出现歧义的部分,将需求确定和明晰,最终以系统化的形式表达用户的需求,给出问题的形式化或半形式化描述。
在结构化分析中有三个要素,分别是:
- 需要使用哪些信息
- 如何系统化地使用这些信息
- 如何估算
下面针对这些我们来逐一阐释这些问题,首先的问题是在结构化分析中需要使用到的信息。
2.1.1. 基本术语
结构化分析方法中有五个必要的组成元素,分别是:
- 数据流:用一组线和箭头代表数据流动的起始,指向等。
- 数据存储:一种数据的静态结构,比如文件、数据库的元素等。
- 数据源/数据潭:系统外的实体,不属于本系统,数据源通俗讲是系统的输入,而数据潭即系统的输出。
- 数据加工:数据加工是对数据进行变换的单元,它代表了一组对数据的操作。
2.1.2. 基本术语的现实映射
这几个基本术语都比较抽象,我们举个例子来说明一下它们在现实生活中的映射。
比如我们要设计一个简单的门系统,这个系统的需求看起来很明确:门嘛,相信正常人都会理解。但实际上所谓的这个门系统就只是定义了一个模糊的抽象层而已,它定义了:
- 数据存储。门其实对应的就是数据存储,它是一种数据上的抽象。实际上一个门还应该包括若干需明确定义的元素,像门的高度、宽度、厚度,门把手旋转的方向等。
- 数据加工。在这个系统中,开门是一种数据操作,对应了我们的数据加工,它是一种过程抽象。因为开门不仅限于开门这个动作,可能还包括了走到门前,按下把手,推动门若干动作,它是对一个过程的抽象。
- 数据流。在这个小系统中,数据或叫信息的流动体现不是很明显。我们再举一个例子,比如一篇博客的展示。首先博主要创作一篇博客,上传到博客园的服务器中,上传的格式可能是
Markdown
或者其他富文本格式,博客文本作为数据在博主与博客园之间流动。而在其他人浏览时,博客园从后端取出博客内容,为博客加上CSS样式,最后由浏览器渲染展示给浏览的人,这其中数据以HTML的格式从博客园的服务器流向了用户的浏览器,这是另一种数据的流动。 - 数据源和数据潭。在上面这个小例子中,对于博客园这个系统来说,其数据源就是博主,博主创作的博客存储在博客园中。其数据潭则是浏览博客的读者。
2.1.3. 模型表达工具
刚刚我们提到了五个抽象的名词,那么在实际使用中,对于这些信息流的抽象层我们该如何发挥他们的作用。换句话说,我们该如何系统化地使用这些信息?这时模型表达工具就很有用了。在结构化的方法中一般使用三种模型表达工具:
- 数据流图。又称为DFD图,它是一种描述数据变换的图形工具,可以囊括如上所述的五种元素。下面是一个数据流图的例子:
- 数据字典。数据字典常用来定义数据流和数据存储的结构,并给出各个数据项的基本数据类型。在数据字典中引入了一些逻辑操作符用于定义数据结构。下面是一个开源论坛文档中提及到的数据字典,它的形式和我们常见的一些接口文档中定义的数据格式非常相似;在web的前后端接口文档中,我们也常常使用类似的说明格式来约束数据的取值与类型。
- 判定树或判定表。它们主要用来描述数据加工需要“做什么”,包括操作执行的条件,频率,流程和出错处理等。
在这三种模型表达工具中,数据流图和数据字典在数据库的设计中可能出现频率更高些,这两种模型工具与数据本身关系很紧密。
这三种模型表达工具结合起来涵盖了系统整体建模的整个流程,下面让我们来使用结构化方法完成一个完整的建模过程。
2.1.4. 结构化分析建模
在对整体建模时,我们首先要借助数据流图构建一个系统功能模型。要完成系统功能模型,我们首先要确定系统的边界——数据源和数据潭;在区分好系统和其边界后,也就建好了系统的顶层流图,这是再开始对整个系统自顶向下逐步求精地建立系统的层次数据流图。这里体现了我们结构化分析方法中的一个重要思想:自顶向下,逐层分解。
我们依旧用博客系统来举例,如果要我们建立一个简单的博客系统,其中:
- 作者可以通过博客系统发布博客;
- 读者可以通过博客系统阅读博客,并且可以留言评论博客;
根据需求画出其数据流图如下:
- 数据流为:撰写的博客,发表的评论
- 数据源为:作者、读者
- 数据潭为:读者
- 数据存储为:博客系统中存储的博客,评论对于的数据库表项
- 数据加工为:整个系统
这时我们的顶层数据流图就已经完成了,但因为此时我们只有一个需求明确,数据操作并不明确的系统。接下来我们就需要将这个模糊不清的系统的功能细化,将不同的数据流与不同的操作连接,而非只是跟系统这个黑盒相连。
我们将博客系统分成两部分,一部分是文章系统,一部分是评论系统,然后将这两部分与它们分别对应的数据流相连,就可以形成一个具体一点的系统,如下图所示。
在完成数据流图的构建后,我们需要使用数据字典来定义数据流、数据存储和数据项。
比如博客 = 博客标题 + 博客正文 + 博客作者 + 时间戳
这就是一个简单的数据字典的定义。
最后需要为数据加工添加约束条件,比如博客的正文必须大于十个字才能发布,与上一次博客编辑的时间差要大于10s才能更新等。
2.2. 结构化设计
说完了结构化分析,下面我们来讲讲结构化设计。
与结构化结构化设计的主要目标就是给出一个软件的解决方案,它主要分为两个主要部分:
- 系统设计:系统设计确定了系统整体模块结构,包括了实现完整系统需要的模块与模块之间的相互调用关系。
- 模块设计:模块设计主要针对各个模块,对每个模块的需求进行清晰的定义与描述。
这两部分关系是由上到下的,先进行系统的设计,再进行各个模块的设计,同时也映证了结构化方法的基本原则之一:自顶向下功能分解。
2.2.1. 系统设计
在系统设计中,主要目标是用于确定各个模块之间的调用关系和系统的整体结构。由于结构化分析中得到的数据流图并不能完全匹配结构化设计的需求,系统设计的目标就是为了过渡这种差异,将上述结构化分析得到的数据流图转换为模块图以供进一步设计。在结构化设计中常采用结构图来描述程序的结构。
结构图的基本成分有模块、调用和数据。下面是一个结构图的示例
2.2.2.模块化
在结构化设计中有一个非常重要的原则,那就是基于模块化原理的高内聚,低耦合。
将数据流图中直接转换得到的各个子模块进行改进,通过模块的分解和合并,力求降低耦合,提高内聚。比如说可以将多个模块公用的子功能可以独立形成一个模块,供这些模块调用。
结构化设计的目标是将一个初始的模块结构图转换为最终的模块结构图,并把各个模块的描述不清的需求确定下来,所以对于设计人员来说结构化设计是一种挑战,设计的质量将直接影响软件系统开发的质量。
介绍完了结构化方法,下面来粗略了解一些面向对象的方法。
3. 面向对象方法
面向对象方法的出现晚于结构化方法,但其发展壮大之迅速是软件开发领域的一个奇迹。它的概念非常明确,它是一种运用对象、类、继承、封装、聚合、 关联、消息、多态性 等概念来构造系统的软件开发方法(引自《面向对象的系统分析(第2版)》,邵维忠, 杨芙清著,清华大学出版社,2006.12.)。在这里博主不展开讲面向对象分析与面向对象设计方法,主要讲述一些它的特点,以为后面的对比进行铺垫。
3.1. 从对象(事物)出发构造软件系统
在面向对象方法中,最重要的概念就是对象的概念:对象是系统中用来描述客观事物的一个实体,它是构成系统的一个基本单位。
对象是构成世界的一个独立的单位,它具有两方面的特征,一方面是静态特征,另一方面是动态特征。
- 静态特征可以表现为对象在定义后拥有了固定的一些,我们称其为事物的属性。
- 动态特征则表现为我们可以在对象中定义对这些固定属性进行操作的服务,这种服务被定义为对象的方法(Method),也可以称之为事物的行为。
3.1. 对外屏蔽内部细节
面向对象的方法中对象的属性和操作是合为一体的,它们一起构成一个独立的实体。对象内部细节对外部来说是不可见的,对象自动维护自己的状态使自己保持正常运转,外部不需要维护对象的状态变化。
4. 结构化方法与面向对象方法比较
上面我们初步了解了两种方法,现在让我们来一起看一下它们的差别。
4.1. 理念不同
对于结构化方法,一切系统都是由信息流构成的。信息流中既包含了一些必要的数据变换,每个信息流都有自己的起点和归宿,又包含了驱动信息流动的数据操作。整个系统可以被看作是通过数据操作把流动的信息流来组织起来的模型。
所以在结构化方法中,信息流的概念很重要,不论是在分析还是在设计时我们都需要小心翼翼地对待信息流动的方法,源头,终点。
而在面向对象方法中,一切系统都是由对象构成的。对象之间的相互作用和相互影响构成了各式各样复杂多变的系统.
4.2. 可维护性不同
结构化的设计关注的是事件的发生,所以更像是面向过程程序设计的风格,比如说C程序的风格。面向对象的设计关注的是对象的功能,比如说Java程序的风格。
在我看来,结构化方法更像是小说,而面向对象的方法更偏向于产品说明书。
说起小说,小说三要素分别是:人物,环境与故事情节。
- 人物可以映射到结构化方法中的数据存储——即底层的数据结构
- 故事可以映射到结构化方法中的数据加工——即作用在
人物
身上的方法 - 而环境则可以映射到结构化方法中的数据流——它负责将故事和人物串起来。
小说该如何吸引人呢?需要我们的人物形象刻画丰满、故事情节动人,都可以吸引我们。因此在我们阅读小说的时候,注重更多的是发生在这个人物身上的情节,当然,环境对于整个故事的推进也很重要。联系面向过程的程序,一个面向过程的程序描写的是什么——以C语言为例,一个主函数,定义局部变量、全局变量,然后调用子函数,子函数的目的是对变量、数据结构进行操作,是发生在该变量以及该数据结构身上的事情,在主函数中,
void open(Door *door)
{
// do...
}
int main()
{
// do...
open(_door);
// do...
}
这段代码描述的是门的“开”行为:一个行为“开”发生在了门上。这是一个故事情节,讲述了这个行为的发生。面向过程关注的是过程的发生,针对我们的软件来说,就是:你有什么样的情节,就编写什么样的软件。
而产品说明书,很明显是在描述一个产品的属性。一个产品的规格如何、他有什么样的功能,列齐了在一个表上,至于你怎么使用那就是使用者的,我把这个产品的规格、功能都呈现给你,随便你去用。不是说用private
保护起来就叫做封装了,它是用来区分内外的。对于外部的调用者,对象本身提供的就是一张产品使用说明书,你只需要知道怎么用,门怎么开,需要多大的力度,门把手需要转多少度,角度最大能长多大,这些都是产品自身的属性。举个例子:
class Door
{
void open()
{
// do...
}
}
这就好比是一张产品的说明书,这个产品的名字叫Door,它呈现的是open这个接口,外部调用者只需要知道,哦,这个东西可以开啊。而对于门来说,它也不care外部是谁在用,它只需要在open时状态正常就可以了。封装区分内外,内部我来维护、外部我不管,当我修改了open内部的东西时,外部的人根本看不出来我改了,他该用照常用,这是由面向对象带来的可维护性。
对于结构化设计来说,因为其建模所需要的就是“过程”和“数据”,而在客观事物中过程与数据恰恰是最容易变化的性质,所以结构化方法的可维护性就要比面向对象的方法可维护性要差。改动A的同时需要B、C分别作出相应的改动。所以在可维护性上,结构化方法不如面向对象的方法。
4.3. 性能不同
在上面我们提到了结构化方法的可维护性较差,在这个需求变化越来越快的时代显得力不从心。但是结构化的设计方法必然是有其优点和长处才能依然立足于软件工程领域的,也是C/C++语言一直被用作底层语言的一个重要特点:足够快,性能高。
1980年至今,CPU性能提高了近10W倍,但内存的访问性能和效率只提高了不足100倍,主要原因其实并不在于内存部件进展较慢,而在于程序对内存的访问效率大大降低。而面向对象方法的一些弊端因为其特性的滥用而凸显出来。与结构化需要即访问不同,面向对象程序要经过很多间接过程才能访问到目标内存地址,这种间接使得有些时候程序并不能充分利用硬件的最佳性能。面向对象方法的性能问题主要体现在以下方面:
过度封装。从好的方面来说,封装简化了系统设计,将复杂问题抽象成较简单的独立对象,通过对象的相互调用去实现方案,但由于对象包含自己封装的数据,使得运行一个逻辑时数据会分散在不同的内存区域,会产生较多的Cache Miss。
数据布局。我们一般会认为,将相互之间关联的数据紧凑地放在一起对缓存友好,然而,在实际应用场景中,我们需要考虑数据的存取模式。通常来说,面向对象中一个方法只会访问一个对象很少的数据,却因为数据布局的问题不得不载入大量的无关数据,这对于缓存是非常不利的。
结构化方法从本质上说与Cache设计的基本原则局部性原理契合,所以结构化的方法更符合计算机的本质,所以它更快;而面向对象化的方法更符合人的思维方式,更有利于代码的实现。
通过以上对比分析我们可以得出一个直观结论:在应用时,对于更接近底层的软件系统,如Linux系统这种对性能要求较高且与计算机核心息息相关的系统,以及那些对代码和可维护性并没有过高要求的(比如一些小型的大作业工程)一般使用结构化的方法和编程模型指导设计就可以;而对于大型的偏向应用层的软件,比如像Office系列软件等需要多人协作,对性能速度要求没有苛刻要求的,为了代码的可维护性与健壮性,使用面向对象的方法更适合。
以上是博主综合了一些文献和博客思考得出的结论,欢迎各位指正。