结构化方法与面向对象方法之比较
结构化方法与面向对象方法的概念都存在已久,但它们至今仍是活跃在一线、影响力极深的软件工程开发方法。结构化方法的核心是模块化思想与自顶向下的设计思想,它将一个复杂的问题进行分解,从而将其拆分为几个规模较小的问题,之后按照同样的方式对于分解后的问题进行进一步拆分,直到某一步中问题已经相对较为独立;而面向对象方法则是将构成问题的事务分解为各个对象,与之前不同的是,每个对象设计的目的不是解决某个问题,而是为了描述某个事物在解决问题的过程中的行为。本文将首先对于两种方法都进行较为详细的介绍,之后结合实例对两种方法的优缺点进行比较。
结构化方法
结构化方法包括结构化分析(SA),结构化设计(SD),结构化程序设计(SP)三方面内容,分别对应于软件开发中的分析、设计与编码阶段。
结构化分析
结构化分析方法(Structured Method)是强调开发方法的结构合理性以及所开发软件的结构合理性的软件开发方法,它给出一组帮助系统分析人员产生功能规约的原理与技术。它一般利用图形表达用户需求,使用的手段主要有一下四种。
1、数据流图(DFD-Data Flow Diagram)
数据流图是描述数据处理过程的工具,是需求理解的逻辑模型的图形表示,它直接支持系统的功能建模。
数据流图中主要图形元素有:
→:数据流—流是数据在系统内传播的路径,因此由一组成分固定的数据组成。如订票单由旅客姓名、年龄、单位、身份证号、日期、目的地等数据项组成。由于数据流是流动中的数据,所以必须有流向,除了与数据存储之间的数据流不用命名外,数据流应该用名词或名词短语命名。
□:数据源(终点)—代表系统之外的实体,可以是人、物或其他软件系统。
○:对数据的加工(处理)—加工是对数据进行处理的单元,它接收一定的数据输入,对其进行处理,并产生输出。
〓:数据存储—表示信息的静态存储,可以代表文件、文件的一部分、数据库的元素等。
建立数据流图步骤如下:
(1)由外向内:先画系统的输入和输出,然后画系统的内部
(2)自顶向下:顺序完成顶层、中间层、底层数据流图
(3)逐层分解
2、数据字典(DD-Data Dictionary)
数据字典是结构化分析方法的核心。数据字典是对所有与系统相关的数据元素的一个有组织的列表,以及精确严格的定义,使用户和系统分析员对输入、输出、存储和中间结果有共同的理解。
数据字典的作用是对数据流图(DFD)中出现的被命名的图形元素的确切解释,通常数据词典包含的信息有:名称、别名、何处使用/如何使用、内容描述、补充信息等。
3、判定树
使用判定树进行描述时,应先从问题定义的文字描述中分清哪些是判定条件,哪些是判定结论,根据描述材料中的连接词找出判定条件之间的从属关系、并列关系、选择关系,根据它们构造判定树。
4、判定表
判定表和判定树功能相似,当数据流图中的加工要依赖于多个逻辑条件的取值,即完成该加工的一组动作是由于某一组条件取值的组合而引发的,使用判定表描述比较适宜。
判定由四部分组成:(1)基本条件;(2)条件项;(3)基本动作;(4)动作项
结构化分析的步骤如下:
①分析当前的情况,做出反映当前物理模型的DFD;
②推导出等价的逻辑模型的DFD;
③设计新的逻辑系统,生成数据字典和基元描述;
④建立人机接口,提出可供选择的目标系统物理模型的DFD;
⑤确定各种方案的成本和风险等级,据此对各种方案进行分析;
⑥选择一种方案;
⑦建立完整的需求规约
结构化设计
在结构化分析阶段,我们生成了数据流图。结构化的设计能方便的将数据流图转换为软件结构图。数据流图从系统中的输入数据流到系统的输出数据流的一连串连续变换形成了一条信息流。根据数据流类型不同,可分为变换型和事务型2类,事务型和变换型数据流的设计步骤基本是大同小异,它们之间主要差别就是从数据流图到软件结构的映射方法不同。因此在进行软件结构设计时,首先对数据流图进行分析,然后判断属于哪一种类型,根据不同的数据流类型,通过一系列映射,把数据流图转换为软件结构图。具体过程见图一。
图一 数据流程图转换为软件结构图基本流程
结构化设计方法是设计软件体系结构的一种系统化的方法,根据不同的映射规则,可以把数据流图变换成软件的初步结构图。得出软件的初始结构图之后,还必须根据结构化设计的基本原则和有关启发规则,对所得到的初始软件结构图进行仔细优化,才能设计出令人满意的软件体系结构。
结构化编码
结构化编码的核心是模块化思想以及自顶向下的程序设计思想,这两者是相辅相成的。现代软件工程的特点之一即是项目规模大,以至于开发人员很难,甚至是无法有效的对代码质量进行把握。因而自顶向下的编码思想被提出,通过对于原始问题进行层层分解,人们不需要直接面对大规模的复杂系统,而是每次只需关心一个局部的问题。而对于这样局部的问题,则可以设计模块来进行解决(在实践中往往是使用函数来实现模块)。
图2 自顶向下分解任务
面向对象方法
与结构化方法类似,面向对象方法包括面向对象分析(OOA)、面向对象设计(OOD)及面向对象程序设计(OOP)3部分内容。
面向对象分析
是在一个系统的开发过程中进行了系统业务调查以后,按照面向对象的思想来分析问题。OOA与结构化分析有较大的区别。OOA所强调的是在系统调查资料的基础上,针对OO方法所需要的素材进行的归类分析和整理,而不是对管理业务现状和方法的分析。
OOA模型由5个层次(主题层、对象类层、结构层、属性层和服务层)和5个活动(标识对象类、标识结构、定义主题、定义属性和定义服务)组成。在这种方法中定义了两种对象类之间的结构,一种称为分类结构,一种称为组装结构。分类结构就是所谓的一般与特殊的关系。组装结构则反映了对象之间的整体与部分的关系。
OOA在定义属性的同时,要识别实例连接。实例连接是一个实例与另一个实例的映射关系。OOA在定义服务的同时要识别消息连接。当一个对象需要向另一对象发送消息时,它们之间就存在消息连接。
OOA的原则主要有以下几条:
(1)抽象:从许多事物中舍弃个别的、非本质的特征,抽取共同的、本质性的特征,就叫作抽象。抽象是形成概念的必须手段。抽象原则有两方面的意义:第一,尽管问题域中的事物是很复杂的,但是分析员并不需要了解和描述它们的一切,只需要分析研究其中与系统目标有关的事物及其本质性特征。第二,通过舍弃个体事物在细节上的差异,抽取其共同特征而得到一批事物的抽象概念。
(2)封装:就是把对象的属性和服务结合为一个不可分的系统单位,并尽可能隐蔽对象的内部细节。
(3)继承:特殊类的对象拥有的其一般类的全部属性与服务,称作特殊类对一般类的继承。
面向对象设计
面向对象设计(Object-Oriented Design,OOD)方法是OO方法中一个中间过渡环节。其主要作用是对OOA分析的结果作进一步的规范化整理,以便能够被OOP直接接受。
面向对象设计(OOD)是一种软件设计方法,是一种工程化规范。其主要包括以下几项工作:
- 决定你要的类;
- 给每个类提供完整的一组操作;
- 明确地使用继承来表现共同点。
由这个定义,我们可以看出:OOD就是“根据需求决定所需的类、类的操作以及类之间关联的过程”。
OOD的目标是管理程序内部各部分的相互依赖。为了达到这个目标,OOD要求将程序分成块,每个块的规模应该小到可以管理的程度,然后分别将各个块隐藏在接口(interface)的后面,让它们只通过接口相互交流。
面向对象程序设计
OOP是在结构化程序设计的基础上,于80年代初涌现的一种程序设计方法,但其真正显示力量和被产业界所重视还是最近几年的事。封装是整个OOP方法的基础,主要用于在数据段外围构造保护层,以限制外界变化的影响,所有的数据访问都由保护层内的过程间接处理。应用程序员不必再按照将程序设计语言逐句拼装的方式来构造整个软件,只需组合、重用由系统程序员开发、可供他人用来装配的软件集成块即可。
OOP 达到了软件工程的三个主要目标:重用性、灵活性和扩展性。为了实现整体运算,每个对象都能够接收信息、处理数据和向其它对象发送信息。OOP 主要有以下主要的概念:
组件:数据和功能一起在运行着的计算机程序中形成的单元,组件在 OOP 计算机程序中是模块和结构化的基础。
1. 抽象性:程序有能力忽略正在处理中信息的某些方面,即对信息主要方面关注的能力。
2. 封装:确保组件不会以不可预期的方式改变其它组件的内部状态;只有在那些提供了内部状态改变方法的组件中,才可以访问其内部状态。每类组件都提供了一个与其它组件联系的接口,并规定了其它组件进行调用的方法。
3. 多态性:组件的引用和类集会涉及到其它许多不同类型的组件,而且引用组件所产生的结果得依据实际调用的类型。
4. 继承性:允许在现存的组件基础上创建子类组件,这统一并增强了多态性和封装性。典型地来说就是用类来对组件进行分组,而且还可以定义新类为现存的类的扩展,这样就可以将类组织成树形或网状结构,这体现了动作的通用性。
结构化方法 V.S 面向对象方法
从上面的介绍与分析中,我们可以总结出:结构化方法中,数据与操作以松散的方式联系在一起,数据往往能够被多个操作访问,它可能在开发人员不知道的情况下被修改(可以想象,在大型多人开发项目中,这种情况会频繁发生),从而导致难于调试的运行错误。此种情况会增大程序理解的复杂度,以及验证程序正确性的成本;而与此相对,面向对象方法,通过使用对象,将数据与操作紧密结合到了一起,每个对象通过数据隐藏,自主管理其状态(即数据),这使得验证程序的正确性成为了可能,同时由于数据处理的局部化,对于程序的理解也更加简单。
除此之外,结构化方法中,程序的基本单元是模块,及处理事务的步骤,在主函数中,只要一次调用各个模块即可解决问题;而面向对象方法中,程序的基本单元是对象,每个对象都刻画了问题处理过程中的某一个事物。
举个例子来说,假如我们要设计一个简单的五子棋游戏。按照结构化的方法,就是首先分析问题的步骤:1、开始游戏; 2、黑子先走; 3、绘制画面;4、判断输赢,如果黑胜则跳到9,否则继续执行5;5、轮到白子;6、绘制画面;7、判断输赢,如果白胜则调到9,否则继续到8;8、返回步骤2;9、输出最后结果。
而如果按照面向对象的方法来设计,首先需要识别出整个系统中有哪些类。那么经过分析,我们发现整个五子棋可以分为 1、黑白双方,这两方的行为是一模一样的;2、棋盘系统,负责绘制画面;3、规则系统,负责判定诸如犯规、输赢等。第一类对象(玩家对象)负责接受用户输入,并告知第二类对象(棋盘对象)棋子布局的变化,棋盘对象接收到了棋子的i变化就要负责在屏幕上面显示出这种变化,同时利用第三类对象(规则系统)来对棋局进行判定。
从这组比较中,我们可以发现,虽然两种方法都比较直观,最终也都解决了问题,但显然面向对象方法设计起来更加自然,因为其基本元素与其功能划分直接产生于现实世界,而结构化方法则需要再对整个流程进行分析,从而才能设计出各个步骤。此外,我们还能够发现,面向对象方法直接考虑的是功能,而非更加细化的步骤,这在减少思考的复杂度的同时,也更便于应对实际需求中经常会遇到的需求变化而导致的相应功能变化;而相应的,由于功能被拆分到各个步骤中去,结构化方法在遇到这种情况时,往往会非常痛苦。
仍以刚刚的系统为例,如果需求方提出,不仅要做五子棋游戏,要做一个棋类游戏集合,即要求游戏规则可以有多种选择(五子棋规则,围棋规则等等)。如果是结构化方法,因为各个模块较为独立,每个模块都需要涉及到游戏规则,而非像面向对象方法中的集中到同一个对象中,那么可以想象,这时如果要更改游戏规则是非常痛苦的(更不用说在此种情况下是要允许用户自由选择游戏规则),甚至不如推倒重做。而如果按照面向对象方法的设计,只需要改动规则对象,增加几个选择分支,使其能够对于不同的胜利条件进行判断即可,对于整个系统来说,其余部分几乎不需要改动。
然而面向对象方法也非毫无缺点,最突出的一点,即是其在性能上往往与结构化方法实现的系统有很大的差距,原因之一在于其天生对于并发的支持力度很弱。由于对象是有状态的,那么每次调用同一方法时,往往会返回不同的结果(如取某个属性的值)。而这就导致并发执行时,各个线程执行的顺序对于程序的正确性至关重要(因而我们需要各种各样的锁来满足开发的需求),因而我们要为了线程安全而降低程序的并发程度,从而拖慢程序速度。而无状态的函数则不会遇到这些问题。另一个原因在于,面向对象方法的提出本就不是为了解决效率问题,而是简化软件设计、开发、维护的过程,这一点从上面的例子中也能够看出。而这样的简化不可避免的会以牺牲效率为代价。而结构化方法,由于从设计阶段开始就直接涉及底层的数据结构和算法,因而可以更加方便地进行优化。
在认识到二者区别的同时,我们也需要意识到结构化方法与面向对象方法并不是相互对立的。首先来说,OOP是SP的超集。一段典型的结构化程序如下:
... Program start var var var function {...} function {...} function {...} main {...} ... Program End
首先是变量定义,之后函数定义,最后主函数中调用各个函数。而与之对应的面向对象程序代码如下:
... Program start
Object {
Var
Var
function {...}
function {...}
function {...}
}
main [...}
... Program End
首先定义类,类中定义属性(对应于变量)与方法(对应于函数),之后在主函数中实例化对象并调用其相应的方法即可。这一特性也使得很多初学者会以面向对象之虚行面向过程之实。我们甚至可以说,面向对象方法是结构化方法发展而来,因为它继承了很多结构化方法中的成功经验:数据抽象、自顶向下、模块化、高内聚、低耦合等。
总结
本文首先介绍了结构化方法与面向对象方法的概念与基本实现步骤。结构化方法的核心在于模块化思想,而面向对象方法核心在于数据与操作的抽象以及封装。通过结合实例的比较,我们发现,面向对象方法更加贴合实际,设计、实现与维护起来相较于结构化方法都更加简单,而其缺点则在于对于并发的支持力度不足,从而导致代码执行效率与结构化方法实现的程序相比较低,往往需要结合后续的分析与优化。然而,我们也要认识到,面向对象方法与结构化方法并不是对立的,从某种角度来说,我们甚至可以将面向对象方法看做是由结构化方法发展而来。面向对象方法继承了很多结构化方法中的成功经验如:数据抽象、自顶向下、模块化、高内聚、低耦合等,因而可以说是结构化方法的实践经验的整合。在实际项目中,结构化方法更为适合小型项目,然而当数据量增大时,数据与处理这些数据的方法之间的分离使程序变得越来越难以理解。对数据处理能力的需求越强,这种分离所造成的负面影响越显著,这时面向对象方法则显得更为合适。