第8章 · 原文阅读

系统分析与设计方法

第8章 系统分析与设计方法

对于架构设计师而言,如何进行系统设计是其“看家本领”,而设计是在对系统进行分析的基础上进行的,否则,设计就是“无米之炊”。从软件开发项目中的角色分配来看,系统架构设计师应该在信息系统项目管理师的协调下,与系统分析师协同工作。

8.1 定义问题与归结模型

软件系统的目的是为了解决问题,因此在建模之初最重要的步骤是对问题的分析与定义,并在此基础上归结模型,这样才能够获得切实有效的模型。定义问题的过程包括:理解真实世界中的问题和用户的需要,并提出满足这些需要的解决方案的过程。

8.1.1 问题分析

问题分析的目标就是在开发之前对要解决的问题有一个更透彻的理解。为了达到这一目标,通常需要经过在问题定义上达成共识,理解问题的本质,确定项目干系人和用户,定义系统的边界和确定系统实现的约束这五个步骤。

1. 在问题定义上达成共识

要检验大家是否在问题的定义上达成了共识,最简单的方法就是把问题写出来,看看是否能够获得大家的认可。而要使得这个过程更加有效,应该将问题用标准化的格式写出来,根据 UP 的建议,应该包括以下几个方面的要素。

问题概述:用简短的几句话,将所理解的问题本质描述出来;

影响:说明该问题将会对哪些项目干系人(Stakeholder,风险承担者)产生影响;

结果:确定问题对项目干系人和商业活动会产生什么样的影响;

优点:概要性地提出解决方案,并列举出该解决方案的主要优点。

在问题定义上达成共识,就能够有效地将开发团队的理解与用户的需求达成一致,这样就能够使得整个系统的开发沿着合理的方向发展。

2. 理解问题的本质

每一句描述都会夹杂着叙述者的个人理解和判断,因此透过表面深入本质,理解问题背后的问题,是问题分析阶段一个十分关键的任务。其中一种技术是“根本原因”分析,这是一种提示问题或其表象的根本原因的系统化方法。在实际的应用中,常使用因果鱼骨图和帕累托图两种方法。

将问题简明扼要地写在右边的方框里;

确定问题潜在原因的主要类别,将它们连到鱼的脊骨上;

用头脑风暴法寻找原因并归类。图 8-1 是鱼骨图的一个示例。

帕累托图是采用直方图的形式,根据问题的相对频率或大小从高往低降序排列,帮助设计师将精力集中在重要的问题上。它为 80%的问题找到关键的 20%的原因,它可以一目了然地显示出各个问题的相对重要程度,有助于预防在解决了一些问题后,却使另外一些问题变得更糟的现象发生。在使用时,通常按照以下步骤进行。

明确问题:也就是前面达成共识的问题定义;

找出问题的各种可能原因:通常可以利用头脑风暴来收集意见,并通过参考以往积累的资料和运营的数据来综合分析;

选择评价标准和考察期限:最常用的评价标准包括频率(占总原因的百分比)和费用(产生的影响),而考察的期限则应具有相应问题的代表性,并不是越长越好;

收集各种原因发生的频率及费用数据;

将原因按照发生的频率或费用从大到小排列起来;

将原因排在横轴上,频率或费用排列在纵轴上,形成如图 8-2 所示的结果。

这样就能够将造成问题的关键原因捕获出来,以便指导设计出更符合需要、更能够解决问题的解决方案。

3. 确定项目干系人和用户

要想有效地解决问题,必须了解用户和其他相关的项目干系人(任何将从新系统或应用的实现中受到实质性影响的人)的需要。不同的项目干系人通常对问题有不同的看法和不同的需要,这些在解决问题时必须加以考虑。事实上,许多项目干系人就是系统的用户,这一部分通常是易于识别的;但还有一部分项目干系人是系统的间接用户,甚至只是受系统影响的商业结果,这一部分不易识别,但十分重要。

在寻找项目干系人时,可以思考:系统的用户是谁?系统的客户(购买者)是谁?还有哪些人会受到系统输出的影响?系统完成并投入使用后,有谁会对它进行评估?还有没有其他系统内部或外部的客户,他们的需要有没有必要去考虑?系统将来由谁来维护?

4. 定义系统的边界

系统的边界是指解决方案系统和现实世界之间的边界。在系统边界中,信息以输入和输出的形式流入系统并由系统流向系统外的用户,所有和系统的交互都是通过系统和外界的接口进行的。在定义系统的边界时,将世界分为两个部分:系统及与系统进行交互的事物。要描述系统的边界有两种方法:一种是结构化分析中的“上下文范围图”,另一种则是面向对象分析中的“用例模型”。

然后,再根据每个参与者的功能需求,识别出代表系统功能的用例,从而界定系统的边界。而关于用例模型的更多细节,请参考 8.4.3 节。

5. 确定系统实现的约束

由于各种因素的存在,会对解决方案的选择造成一定的限制,称这种限制为约束。每条约束都将影响到最后的解决方案的形成,甚至会影响是否能够提出解决方案。

在考虑约束时,首先应该考察到不同的约束源,其中包括进度、投资收益、人员、设备预算、环境、操作系统、数据库、主机和客户机系统、技术问题、行政问题、已有软件、公司总体战略和程序、工具和语言的选择、人员及其他资源限制等。

8.1.2 问题定义

通过对问题进行细致周密的分析,就可以对其进行综合的定义。对于一个问题的完整定义,通常应包括目标、功能需求和非功能需求三个方面。

1. 目标

目标是指构建系统的原因,它是最高层次的用户需求,是业务上的需要,而功能、性能需求则必须是以某种形式对该目标做出贡献。在描述目标时,应该注意以下几个方面。

优势:目标应该不仅仅是解决问题,还要提供业务上的优势;

度量:不仅要说明业务的优势,而且还必须提供度量这种优势的标准;

合理性:要确保完成解决方案所需的工作量少于所获得的业务优势,这才是合理的解决方案;

可行性:要探寻能够满足度量标准的解决方案;

可达成性:对于组织而言,是否具备获取该系统的技能,构建完成后是否能够操作它。

例如,表 8-1 就是一个很好的目标描述的例子。

2. 功能需求

功能需求是用来指明系统必须做的事情,只有这些行为的存在,才有系统存在的价值。

功能需求应该源于业务需求,它只与问题域相关,与解决方案域无关。也就是说,功能需求是在与用户或某个业务人员交谈时,他们所描述的内容是为了完成他们某部分的工作而必须做的事情。而在设计解决方案时,会遇到一些限制条件,这些东西也是“系统需求” 的一部分,不过应该是设计约束或非功能需求形式指定。

在规定功能需求时要注意其详细程度。由于这些需求是业务需求,因此应该由业务人员来验证。也就是说,用户应该能够指明系统要达到有用的程度,功能是否已经足够;考虑到工作的成果,它的功能是否正确。另外,在描述功能需求时,应该注意需求的二义性。而二义性主要体现在以下几个方面。

希赛教育专家提示:在检查功能需求的二义性时,一种有效的方法是大声地朗读出来,大家一起边听边进行讨论,这样可以不断地优化。

3. 非功能需求

非功能需求是系统必须具备的属性,这些属性可以看作是一些使产品具有吸引力、易用、快速或可靠的特征或属性。非功能需求并不改变产品的功能,它是为工作赋予特征的。在识别功能需求和非功能需求时,有一种十分有用的思维模式:功能需求是以动词为特征的,而非功能性需求则是以副词为特征的。非功能需求主要包括以下几种。

8.2 需求分析与软件设计

需求分析是软件生命周期中相当重要的一个阶段。根据 Standish Group 对 23000 个项目进行的研究结果表明,28%的项目彻底失败,46%的项目超出经费预算或者超出工期,只有约 26%的项目获得成功。需求分析工作在整个软件开发生命周期中有着十分重要的意义。

而在这些高达 74%的不成功项目中,有约 60%的失败是源于需求问题,也就是差不多有一半的项目都遇到了这个问题,这一可怕的现象引起人们对需求分析的高度重视。需求分析阶段的主要任务是通过开发人员与用户之间的广泛交流,不断澄清一些模糊的概念,最终形成一个完整的、清晰的、一致的需求说明。

而当明确了用户的需求之后,下一步的任务就是对未来的软件系统进行设计,它是确定系统实现的关键工作。需求分析和设计的方法对软件开发过程而言是十分重要的,因此必须扎实地掌握它。

需求分析与软件设计是软件生存期中最重要的两个步骤,需求分析解决的是“做什么”

的问题,系统设计则是解决“怎么做”的问题。

8.2.1 需求分析的任务与过程

需求分析所要做的工作是深入描述软件的功能和性能,确定软件设计的限制和软件同其他系统元素的接口细节,定义软件的其他有效性需求,细化软件要处理的数据域。用一句话概括就是:需求分析主要是确定待开发软件的功能、性能、数据、界面等要求。需求分析的实现步骤通常包括:获取当前系统的物理模型,抽象出当前系统的逻辑模型,建立目标系统的逻辑模型三个部分。具体来说,需求分析阶段的工作可以分成 4 个方面:

1. 需求的分类

什么是软件的需求呢?软件需求就是系统必须完成的事及必须具备的品质。具体来说,软件需求包括功能需求、非功能需求和设计约束三方面内容。各种需求的概念示意图如图8-4 所示。

图8-4

功能需求:是指系统必须完成的那些事,即为了向它的用户提供有用的功能,产品必须执行的动作。

非功能需求:是指产品必须具备的属性或品质,如性能、响应时间、可靠性、容错性、扩展性等。

设计约束:也称为限制条件、补充规约,这通常是对解决方案的一些约束说明,例如必须采用国有自主知识版权的数据库系统,必须在 UNIX 操作系统之下运行等。

除了这三种需求之外,还有业务需求、用户需求、系统需求这三个处于不同层面的概念,充分地理解这样的模型才能够更加清晰地理清需求的脉络。

业务需求(Business Requirement):是指反映组织机构或客户对系统、产品高层次的目标要求,通常问题定义本身就是业务需求。

用户需求(User Requirement):是指描述用户使用产品必须要完成什么任务,怎么完成的需求,通常是在问题定义的基础上进行用户访谈、调查,对用户使用的场景进行整理,从而建立从用户角度出发的需求。

系统需求(System Requirement):是从系统的角度来说明软件的需求,它包括用特性说明的功能需求、质量属性、非功能需求及设计约束。

分析师经常围绕着“功能需求”来展开工作,而功能需求大部分都是从“系统需求” 的角度来分析与理解的,也就是用“开发人员”的视角来理解需求。但要想真正地得到完整的需求,仅戴上“开发人员”的眼镜是不够的,还需要“领域专家”的眼镜,要从更高的角度来理解需求,这就是“业务需求”;同时还应该更好地深入用户,了解他们的使用场景,了解他们的想法,这就是“用户需求”。这是一个理解层次的问题,并不仅仅是简单的概念。

2. 需求工程

需求工程就是包括创建和维护系统需求文档所必需的一切活动的过程,主要包括需求开发和需求管理两大工作。

这两个方面是相辅相成的,需求开发是主线,是目标;需求管理是支持,是保障。换句话说,需求开发是努力更清晰、更明确地掌握客户对系统的需求;而需求管理则是对需求的变化进行管理的过程。

3. 需求分析方法

需求分析的方法可谓种类繁多,不过如果按照分解方式的不同,可以很容易地划分出几种类型。本节先从分析方法发展的历史,对其建立一个概要性的认识,在本章的后面几节中将详细说明最具有代表性的结构化分析与设计、面向对象分析与设计两种方法。

8.2.2 如何进行系统设计

当设计者拿到一个需求,他如何开展系统设计呢?许多想进入系统分析与设计的年轻人以为自己知道了面向对象、统一建模语言、设计模式等新鲜深奥的名词就可以进行设计了,可是掌握工具和技能绝不是成为优秀设计者的充分条件,甚至也不是必要条件。遗憾的是这里没有捷径,只有设计者在实践中不断学习和总结。而在实践中,系统设计与其说是在设计,不如说是在选择和妥协。

妥协,就是在各个系统目标之间找到一个平衡点。系统目标包括但不限制于功能、性能、健壮性、开发周期、交付日期等。不幸的是,这些目标往往是矛盾的,提高软件性能直接意味着开发周期的增加、交付日期的推迟,盲目地增加功能可能导致性能降低,维护成本提高。软件设计者的难题在于在如此众多的目标之间找到这个平衡点,并且明确知道如何设计能实现这个平衡,既可以让投资者觉得在预算之内,又能让用户相对满意。可行性分析阶段应该已经论述了这样一个平衡点,可是如果设计者发现没有这样一个平衡点,如同没有一个设计者能让人骑着自行车到月球上去,那么设计者只能提出放弃某个方面的过度要求,否则系统要遭受必然失败的命运。更多的情况是没有经验的设计者不知道是否存在这些平衡点,更不知道如何利用合理的设计及有效的工具来达到平衡。因此设计者需要了解可以解决问题的各种方案,并清楚知道各个方案的效果、成本、缺点,以及这些方案的区别,并在各种方案中进行选择。而这些,不是一个人能在一两天了解的。

没有一个设计者会完全重新开始设计一个系统,他们总参考多个与目标系统相类似的系统,再从中进行甄别、取舍和补充来作为新系统的设计。人们发现一个优秀的设计者似乎能在听完需求后就立即构想出目标系统的框架,这并不是因为他聪明或者比不知所措的设计者新手多一个脑袋,而是因为他在平时已经了解大量的系统,对各种设计的优缺点、局限性也了然于胸,能够把以前的设计根据需要再次使用。所以要成为优秀的设计者,了解、掌握、消化、总结前人和自己以前的设计成果是最好的方法,这似乎也是唯一的方法。

设计者的苦恼有时候和编程人员一样。计算机系统的发展如此迅速,解决方案也越来越多,如同编程语言的发展,同时,随着人类社会的进步,投资者和客户也提出了越来越高的要求,这又需要设计者不断学习、创造新的方案。

但系统设计也并非没有规律可以遵循,如同幸福的家庭都很相似,不幸的家庭各有各的不幸,人们在实践中发现优秀的系统设计一般在以下几个方面都很出色。

而且,更幸运的是,也有一些技术能够改进系统设计,这些方法包括:降低复杂性、通过合约进行设计、原型化设计、错误树分析等。

8.2.3 软件设计的任务与活动

软件设计是一个把软件需求变换成软件表示的过程。最初这种表示只是描绘出软件的总体框架,然后再进一步细化,并在此框架中填入细节。

1.软件设计的两个阶段从工程管理角度,软件设计可以分为两个步骤:

2. 主要的设计方法比较

在结构化设计风行的时代,主流的设计方法还包括 Jackson 方法和 Parnas 方法。结构化方法侧重于“模块相对独立且功能单一,使模块间联系弱、模块内联系强”;而 Jackson 方法则是从数据结构导出模块结构;Parnas 方法的主要思想则是将可能引起变化的因素隐藏在有关模块内部,使这些因素变化时的影响范围受到限制,它只提供了重要的设计准则,但没有规定出具体的工作步骤。

而近年来,对象技术凭借其对数据的高效封装及良好的消息机制,实现了高内聚、低耦合的系统设计,成了现代软件设计的主流方法学。

8.3 结构化分析与设计

结构化分析与设计方法是一种面向数据流的需求分析和设计方法,它适用于分析和设计大型数据处理系统,是一种简单、实用的方法,曾获得广泛的应用。

8.3.1 结构化分析

结构化分析方法的基本思想是自顶向下逐层分解。分解和抽象是人们控制问题复杂性的两种基本手段。对于一个复杂的问题,人们很难一下子考虑问题的所有方面和全部细节,通常可以把一个大问题分解成若干个小问题,每个小问题再分解成若干个更小的问题,经过多次逐层分解,每个最底层的问题都是足够简单、容易解决的,于是复杂的问题也就迎刃而解了。这个过程就是分解过程。

结构化分析与面向对象分析方法之间的最大差别是:结构化分析方法把系统看作一个过程的集合体,包括人完成的和电脑完成的;而面向对象方法则把系统看成一个相互影响的对象集。结构化分析方法的特点是利用数据流图来帮助人们理解问题,对问题进行分析。

结构化分析一般包括以下工具:数据流图(Data Flow Diagram,DFD)、数据字典(DataDictionary,DD)、结构化语言、判定表、判定树。在接下来的部分将对它们一一做简单介绍。

结构化系统分析方法从总体上来看是一种强烈依赖数据流图的自顶向下的建模方法。它不仅是需求分析技术,也是完成需求规格化的有效技术手段。

1. 结构化分析的工作步骤

在介绍具体的结构化分析方法之前,先对如何进行结构化分析做一个总结性描述,以帮助大家更好地应用该方法。

2. 数据流图

DFD 是一种图形化的系统模型,它在一张图中展示信息系统的主要需求,即输入、输出、处理(过程)、数据存储。由于从 DFD 中可以很容易地看出系统紧密结合的各个部分,而且整个图形模式只有 5 个符号需要记忆,所以深受分析人员的喜爱,因而广为流行。

如图 8-5 所示,DFD 中包括以下几个基本元素。

过程:也称为加工,一步步地执行指令,完成输入到输出的转换。

外部实体:也称为源/宿,系统之外的数据源或目的。

数据存储:也称为文件,存放数据的地方,一般是以文件、数据库等形式出现。

数据流:从一处到另一处的数据流向,如从输入或输出到一个过程的数据流。

实时连接:当过程执行时,外部实体与过程之间的来回通信。

希赛教育专家提示:不同的参考书籍中关于 DFD 的符号可能有些不一样,其中加工、外部实体、数据存储和数据流是公共的部分。

数据流图的层次如图 8-6 所示。

Context 图用来描述系统有什么输入、输出数据流,与哪些外部实体直接相关,可以把整个系统的范围勾画出来。

图 8-8 是在 Context 图的基础上做的第一次分解,而在 Context 图中只有一个过程,那就是系统,将其编号为 0。而接下来对 Context 图进行的分解,其实就是对这个编号为 0的过程进行更细化的描述,在这里引入了新的过程、数据存储,为了能够区分其位置的级别,在这层次上的过程将以 1、2、3 为序列进行编号。

由于这是对过程 0 的分解,因此也称之为 DFD 0 层图。而可以根据需要对 DFD 0 层图上的过程(编号为 1、2、3)进行类似的分解,那么就称之为 DFD 1 层图,在 DFD 1 层图中引入的新过程,其编号规则就是 1.1,1.2…,以及 2.1,2.2…,以此类推,直到完成分析工作。

另外,这里存在一个很关键的要点,那就是 DFD 0 层图是 Context 图的细化,因此所有的输入和输出应该与 Context 图完全一致,否则就说明存在着错误。

画系统的输入和输出:就是在图的边缘标出系统的输入、输出数据流。这一步其实是决定研究的内容和系统的范围。在画的时候,可以先将尽可能多的输入、输出画出来,然后再删除多余的,增加遗漏的。

画数据流图的内部:将系统的输入、输出用一系列的处理连接起来,可以从输入数据流画向输出数据流,也可以从中间画出去。

为每一个数据流命名:命名的好坏与数据流图的可理解性密切相关,应避免使用空洞的名字。

为加工命名:注意应该使用动宾短语。

不考虑初始化和终点,暂不考虑出错路径等细节,不画控制流和控制信息。

3.细化记录DFD 部件

为了更好地描述DFD 的部件,结构化分析方法还引入了数据字典、结构化语言及决策树、决策表等方法。通过使用这些工具,能对数据流图中描述不够清晰的地方进行有效的补充。其其中数据字典应用最为广泛,下面将详细说明数据字典的相关使用方法。

数据字典技术是一种很实用、有效的表达数据格式的手段。它是对所有与系统相关的数据元素的一个有组织的列表和精确严格的定义,使得用户和系统分析员对于输入、输出、存储成分和中间计算机有共同的理解。通常数据字典的每一个条目中包括以下信息。

=:由…构成。

+:和,代表顺序连接的关系。

[ | ]:或,代表从中选择一个。

{}*:n 次重复。

():代表可选的数据项。

:表示特定限制的注释。

下面就是一个数据字典的实例:

客户基本信息=客户编号+客户名称+身份证号码+手机+家庭电话

客户编号 = {0…9}8

客户名称 = {字}4

身份证号码 = [{0…9}15|{0…9}18]

手机 = [{0…9}11|{0…9}12]

家庭电话 =(区号)+本地号区号 = {0…9}4

本地号 = [{0…9}7|{0…9}8]

8.3.2 结构化设计

结构化设计包括架构设计、接口设计、数据设计和过程设计等任务。它是一种面向数据流的设计方法,是以结构化分析阶段所产生的成果为基础,进一步自顶而下、逐步求精和模块化的过程。

1. 概要设计与详细设计的主要任务

概要设计阶段的主要任务是设计软件的结构、确定系统是由哪些模块组成,以及每个模块之间的关系。它采用结构图(包括模块、调用、数据)来描述程序的结构,此外还可以使用层次图和 HIPO(层次图加输入/处理/输出图)。

整个过程主要包括:复查基本系统模型、复查并精化数据流图、确定数据流图的信息流类型(包括交换流和事务流)、根据流类型分别实施变换分析或事务分析、根据软件设计原则对得到的软件结构图进一步优化。

而详细设计阶段的主要任务则是确定应该如何具体地实现所要求的系统,得出对目标系统的精确描述。它采用自顶向下、逐步求精的设计方式和单入口单出口的控制结构。常使用的工具包括程序流程图、盒图、PAD(Problem Analysis Diagram,问题分析图)、PDL(ProgramDesign Language,程序设计语言)。

2. 结构图

如图 8-9 所示,结构图的基本成分包括模块、调用(模块之间的调用关系)和数据(模块间传递及处理数据信息)。

结构图是在需求分析阶段产生的数据流图的基础上进行进一步的设计。它将 DFD 图中的信息流分为两种类型。

变换流:信息首先沿着输入通路进入系统,并将其转换为内部表示,然后通过变换中心(加工)的处理,再沿着输出转换为外部形式离开系统。具有这种特性的加工流就是变换流。

事务流:信息首先沿着输入通路进入系统,事务中心根据输入信息的类型在若干个动作序列(活动流)中选择一个执行,这种信息流称为事务流。

3. 程序流程图和盒图

程序流程图和盒图都是用来描述程序的细节逻辑的,其符号如图 8-10 所示。

程序流程图的特点是简单、直观、易学,但它的缺点也正是由于其随意性而使得画出来的流程图容易成为非结构化的流程图。而盒图正是为了解决这一问题设计的,它是一种符合结构化程序设计原则的图形描述工具。

盒图的主要特点是功能域明确、无法任意转移控制、容易确定全局数据和局部数据的作用域、容易表示嵌套关系、可以表示模块的层次结构。但它也带来了一个副作用,那就是修改相对比较困难。

4. PAD 和 PDL

PAD 是问题分析图的缩写,它符合自顶向下、逐步求精的原则,也符合结构化程序设计的思想,它最大的特点在于能够很方便地转换为程序语言的源程序代码。

PDL 则是语言描述工具的缩写,它和高级程序语言很相似,也包括数据说明部分和过程部分,还可以带注解等成分,但它是不可执行的。PDL 是一种形式化语言,其控制结构的描述是确定的,但内部的描述语法是不确定的。PDL 通常也被称为伪码。

8.3.3 模块设计

在结构化方法中,模块化是一个很重要的概念,它是将一个待开发的软件分解成为若干个小的简单部分——模块,每个模块可以独立地开发、测试。这是一种复杂问题的“分而治之”原则,其目的是使程序的结构清晰、易于测试与修改。

具体来说,模块是指执行某一特定任务的数据结构和程序代码。通常将模块的接口和功能定义为其外部特性,将模块的局部数据和实现该模块的程序代码称为内部特性。而在模块设计时,最重要的原则就是实现信息隐蔽和模块独立。模块经常具有连续性,也就意味着作用于系统的小变动将导致行为上的小变化,同时规模说明的小变动也将影响到一小部分模块。

1. 信息隐蔽原则

信息隐蔽是开发整体程序结构时使用的法则,即将每个程序的成分隐蔽或封装在一个单一的设计模块中,并且尽可能少地暴露其内部的处理。通常将难的决策、可能修改的决策、数据结构的内部连接以及对它所做的操作细节、内部特征码、与计算机硬件有关的细节等隐蔽起来。通过信息隐蔽可以提高软件的可修改性、可测试性和可移植性,它也是现代软件设计的一个关键性原则。

2. 模块独立性原则

模块独立是指每个模块完成一个相对独立的特定子功能,并且与其他模块之间的联系最简单。保持模块的高度独立性,也是设计过程中的一个很重要的原则。通常用耦合(模块之间联系的紧密程度)和内聚(模块内部各元素之间联系的紧密程度)两个标准来衡量,设计的目标是高内聚、低耦合。

模块的内聚类型通常可以分为 7 种,根据内聚度从高到低排序,如表 8-2 所示。

与此相对应的,模块的耦合性类型通常也分为 7 种,根据耦合度从低到高排序,如表8-3 所示。

除了满足以上两大基本原则之外,通常在模块分解时还需要注意:保持模块的大小适中,尽可能减少调用的深度,直接调用该模块的个数应该尽量大,但调用其他模块的个数则不宜过大;保证模块是单入口、单出口的;模块的作用域应该在控制域之内;功能应该是可预测的。

8.4 面向对象的分析与设计

面向对象方法是一种非常实用的软件开发方法,它一出现就受到软件技术人员的青睐,现已成为计算机科学研究的一个重要领域,并逐渐成为软件开发的一种主要方法。面向对象方法以客观世界中的对象为中心,其分析和设计思想符合人们的思维方式,分析和设计的结构与客观世界的实际比较接近,容易被人们接受。在面向对象方法中,分析和设计的界面并不明显,它们采用相同的符号表示,能够方便地从分析阶段平滑地过渡到设计阶段。此外,在现实生活中,用户的需求经常会发生变化,但客观世界的对象及对象间的关系比较稳定,因此用面向对象方法分析和设计的结构也相对比较稳定。

8.4.1 面向对象的基本概念

1. 对象和类

对象是系统中用来描述客观事物的一个实体,它由对象标识(名称)、属性(状态、数据、成员变量)和服务(操作、行为、方法)三个要素组成,它们被封装为一个整体,以接口的形式对外提供服务。

在现实世界中,每个实体都是对象,如学生、书籍、收音机等;每个对象都有它的操作,例如书籍的页数,收音机的频道、按钮等属性,以及收音机的切换频道等操作。

而类则是对具有相同属性和服务的一个或一组对象的抽象。类与对象是抽象描述和具体实例的关系,一个具体的对象被称为类的一个实例。在系统设计过程中,类可以分为三种类型,分别是实体类、边界类和控制类。

实体类是对用户来说最有意义的类,通常采用业务领域术语命名,一般来说是一个名词,在用例模型向领域模型的转化中,一个参与者一般对应于实体类。通常可以从 SRS 中的那些与数据库表(需要持久存储)对应的名词着手来找寻实体类。通常情况下,实体类一定有属性,但不一定有操作。

控制类将用例的特有行为进行封装,控制对象的行为与特定用例的实现密切相关,当系统执行用例的时候,就产生了一个控制对象,控制对象经常在其对应的用例执行完毕后消亡。

通常情况下,控制类没有属性,但一定有方法。

边界类用于系统接口与系统外部进行交互,边界对象将系统与其外部环境的变更(例如,与其他系统的接口的变更、用户需求的变更等)分隔开,使这些变更不会对系统的其他部分造成影响。通常情况下,边界类可以既有属性也有方法。

2. 继承与泛化

继承是面向对象方法中重要的概念,用来说明特殊类(子类)与一般类(父类)的关系,而通常用泛化来说明一般类与特殊类的关系,也就是说它们是一对多关系。

如图 8-11 所示,“交通工具”是“自行车”和“小轿车”的泛化;“自行车”和“小轿车”从“交通工具”中继承。

3. 多态与重载

多态(即多种形式)性是指一般类中定义的属性或服务被特殊类继承后,可以具有不同的数据类型或表现出不同的行为,通常是使用重载和改写两项技术来实现的。一般有4 种不同形式的多态,如表 8-4 所示。

注 1:重载也称为过载、重置;

注 2:参数多态和包含多态称为通用多态,重载多态和强制多态称为特定多态。

希赛教育专家提示:虽然重载和改写都是在多种潜在的函数体中,选择和调用某一个函数或方法并对其进行执行,但它们的本质区别在于:重载是编译时执行的(静态绑定),而改写则是运行时选择的(动态绑定)。

4. 模板类

也称为类属类,它用来实现参数多态机制。一个类属类是关于一组类的一个特性抽象,它强调的是这些类的成员特征中与具体类型无关的那些部分,而用变元来表示与具体类型有关的那些部分。

5. 消息和消息通信

消息就是向对象发出的服务请求,它通常包括提供服务的对象标识、消息名、输入信息和回答信息。消息通信则是面向对象方法学中的一个重要原则,它与对象的封装原则密不可分,为对象间提供了唯一合法的动态联系的途径。

8.4.2 面向对象分析

面向对象分析的目标是开发一系列模型,这些模型描述计算机软件,当它工作时以满足一组客户定义的需求。对象技术的流行,演化出了数十种不同的 OOA 方法,每个方法都引入了一个产品或系统分析的过程、一组过程演化的模型及使软件工程师能够以一致的方式创建每个模型的符号体系。其中比较流行的方法包括 OMT、OOA、OOSE、Booch 方法等,而OMT、OOSE、Booch 最后则统一成为 UML。

1. OOA/OOD 方法

这是由 Peter Coad 和 Edward Yourdon 提出的,OOA 模型中包括主题、对象类、结构、属性和服务 5 个层次,需经过标识对象类、标识结构与关联(包括继承、聚合、组合、实例化等)、划分主题、定义属性、定义服务 5 个步骤来完成整个分析工作。

OOD 中将继续贯穿 OOA 中的 5 个层次和 5 个活动,它由人机交互部件、问题域部件、任务管理部件、数据管理部件 4 个部分组成,其主要的活动就是这 4 个部件的设计工作。

设计问题域部分:OOA 的结果恰好是 OOD 的问题域部件,分析的结果在 OOD 中可以被改动或增补,但基于问题域的总体组织框架是长时间稳定的;

设计人机交互部件:人机交互部件在上述结果中加入人机交互的设计和交互的细节,包括窗口和输出报告的设计。可以用原型来帮助实际交互机制进行开发和选择;

设计任务管理部分:这部分主要是识别事件驱动任务,识别时钟驱动任务,识别优先任务和关键任务,识别协调者,审查每个任务并定义每个任务。

设计数据管理部分:数据管理部分提供了在数据管理系统中存储和检索对象的基本结构,其目的是隔离数据管理方法对其他部分的影响。

2. Booch 方法

Booch 认为软件开发是一个螺旋上升的过程,每个周期中包括标识类和对象、确定类和对象的含义、标识关系、说明每个类的接口和实现 4 个步骤。它的模型中主要包括如表 8-5所示的几种图形。

Booch 方法的开发过程是一个迭代的、渐进式的系统开发过程,它可以分为宏过程和微过程两类。宏过程用于控制微过程,是覆盖几个月或几周所进行的活动,它包括负责建立核心需求的概念化,为所期望的行为建立模型的分析,建立架构的设计,形成实现的进化,以及管理软件交付使用的维护等 5 个主要活动。

而微过程则基本上代表了开发人员的日常活动,它由 4 个重要、没有顺序关系的步骤组成:在给定的抽象层次上识别出类和对象,识别出这些类和对象的语义,识别出类间和对象间的关系,实现类和对象。

3. OMT 方法

OMT 是对象建模技术的缩写,它是由 Jam Rambaugh 及其同事合作开发的,它主要用于分析、系统设计和对象设计。包括对象模型(静态的、结构化的系统的“数据”性质,通常采用类图)、动态模型(瞬时的、行为化的系统“控制”性质,通常使用状态图)和功能模型(表示变化的系统的“功能”性质,通常使用数据流图)。OMT 方法的三大模型如表 8-6所示。

4.OOSE 方法OOSE 是面向对象软件工程的缩写,它是由 Ivar Jacobson 提出的。它在 OMT 的基础上,对功能模型进行了补充,提出了“用例”的概念,最终取代数据流图进行需求分析和建立功能模型。

8.4.3 统一建模语言

统一建模语言(Unified Modeling Language,UML)是用于系统的可视化建模语言,它将OMT、OOSE 和 Booch 方法中的建模语言和方法有机地融合在一起,是国际统一的软件建模标准。虽然它源于 OO 软件系统建模领域,但由于其内建了大量扩展机制,也可以应用于更多的领域中,例如工作流程、业务领域等。

1. UML 是什么

UML 是一种语言:UML 在软件领域中的地位与价值就像“1、2、3、+、、…”等符号在数学领域中的地位一样。它为软件开发人员之间提供了一种用于交流的词汇表和一种用于软件蓝图的标准语言。

UML 是一种可视化语言:UML 只是一组图形符号,它的每个符号都有明确语义,是一种直观、可视化的语言。

UML 是一种可用于详细描述的语言:UML 所建的模型是精确的、无歧义和完整的,因此适合于对所有重要的分析、设计和实现决策进行详细描述。

UML 是一种构造语言:UML 虽然不是一种可视化的编程语言,但其与各种编程语言直接相连,而且有较好的映射关系,这种映射允许进行正向工程、逆向工程。

UML 是一种文档化语言:它适合于建立系统架构及其所有的细节文档。

2. UML 的结构

UML 由构造块、公共机制和架构三个部分组成。

建模元素:包括结构事物(类、接口、协作、用例、活动类、组件、节点等)、行为事物(交互、状态机)、分组事物(包)、注释事物。

关系:包括关联关系、依赖关系、泛化关系、实现关系。

图:UML 2.0 包括 14 种不同的图,分为表示系统静态结构的静态模型(包括类图、对象图、包图、构件图、部署图、制品图),以及表示系统动态结构的动态模型(包括对象图、用例图、顺序图、通信图、定时图、状态图、活动图、交互概览图)。

规格说明:规格说明是元素语义的文本描述,它是模型的重要组成部分。

修饰:UML 为每一个模型元素设置了一个简单的记号,还可以通过修饰来表达更多的信息。

公共分类:包括类元与实体(类元表示概念,而实体表示具体的实体)、接口和实现(接口用来定义契约,而实现就是具体的内容)两组公共分类。

扩展机制:包括约束(添加新规则来扩展元素的语义)、构造型(用于定义新的 UML建模元素)、标记值(添加新的特殊信息来扩展模型元素的规格说明)。

逻辑视图:以问题域的语汇组成的类和对象集合。

进程视图:可执行线程和进程作为活动类的建模,它是逻辑视图的一次执行实例。

实现视图:对组成基于系统的物理代码的文件和组件进行建模。

部署视图:把组件物理地部署到一组物理的、可计算的节点上。

用例视图:最基本的需求分析模型。

3. 用例图基础

用例是什么呢?Ivar Jacobson 是这样描述的:

“用例实例是在系统中执行的一系列动作,这些动作将生成特定参与者可见的价值结果。一个用例定义一组用例实例。”首先,从定义中得知用例是由一组用例实例组成的,用例实例也就是常说的“使用场景”,就是用户使用系统的一个实际的、特定的场景。其次,可以知道,用例应该给参与者带来可见的价值,这点很关键。最后,用例是在系统中的。

而用例模型描述的是外部参与者所理解的系统功能。用例模型用于需求分析阶段,它的建立是系统开发者和用户反复讨论的结果,表明了开发者和用户对需求规格达成的共识。图8-12 就是一个用例图的例子。

图8-12 中的“图书管理员”就是参与者。对于该系统来说,可能可以充当图书管理员角色的有多个人,由于他们对系统均起着相同的作用,扮演相同的角色,因此只用一个参与者来表示。切忌不要为每一个可能与系统交互的真人画出一个参与者。

每个参与者的任务是什么?

有参与者将要创建、存储、修改、删除或读取系统中的信息吗?

什么用例会创建、存储、修改、删除或读取这个信息?

参与者需要通知系统外部的突然变化吗?

需要通知参与者系统中正在发生的事情吗?

什么用例将支持和维护系统?

所有的功能需求都对应到用例中了吗?

系统需要何种输入/输出?输入从何处来?输出到何处?

当前运行系统的主要问题是什么?

包含关系:当可以从两个或两个以上的原始用例中提取公共行为,或者发现能够使用一个组件来实现某一个用例的部分功能是很重要的事时,应该使用包含关系来表示。所提取出来的公共行为称为抽象用例。包含关系的例子如图 8-13 所示。

扩展关系:如果一个用例明显地混合了两种或两种以上的不同场景,即根据情况可能发生多种事情。可以将这个用例分为一个主用例和一个或多个辅用例,描述可能更加清晰。扩展关系的例子如图 8-14 所示。

希赛教育专家提示:此处介绍的包含和扩展关系属于用例之间所特有的关系,因为用例也是 UML 的一种结构事物,因此,事物之间的 4 种基本关系对用例也是适用的。

4. 类图和对象图基础

在面向对象建模技术中,将客观世界的实体映射为对象,并归纳成一个个类。类、对象和它们之间的关联是面向对象技术中最基本的元素。对于一个想要描述的系统,其类模型和对象模型揭示了系统的结构。在 UML 中,类和对象模型分别由类图和对象图表示。类图技术是 OO 方法的核心。图 8-15 是一个类图的实例。

类的获取和命名:最顶部的格子包含类的名字。类的命名应尽量用应用领域中的术语,应明确、无歧义,以利于开发人员与用户之间的相互理解和交流。

类的属性:中间的格子包含类的属性,用以描述该类对象的共同特点。该项可省略。图 8-15中“书籍”类有“书名”、“书号”等属性。UML 规定类的属性的语法为: “可见性属性名:类型 = 默认值 {约束特性}”。

可见性包括 Public、Private 和 Protected,分别用+、-、#号表示。

类型表示该属性的种类:它可以是基本数据类型,例如整数、实数、布尔型等,也可以是用户自定义的类型。一般它由所涉及的程序设计语言确定。约束特性则是用户对该属性性质的一个约束说明。例如“{只读}”说明该属性是只读属性。

类的操作(Operation):该项可省略。操作用于修改、检索类的属性或执行某些动作。操作通常也被称为功能,但是它们被约束在类的内部,只能作用到该类的对象上。操作名、返回类型和参数表组成操作界面。UML 规定操作的语法为:“可见性:操作名(参数表):返回类型 {约束特性}”。

类图描述了类和类之间的静态关系。定义了类之后,就可以定义类之间的各种关系了。

在类中,依赖由多种原因引起,如:一个类向另一个类发消息;一个类是另一个类的数据成员;一个类是另一个类的某个操作参数。如果一个类的界面改变,它发出的任何消息可能不再合法。

在 UML 中,对泛化关系有 3 个要求:

子类应与父类完全一致,父类所具有的关联、属性和操作,子类都应具有。

子类中除了与父类一致的信息外,还包括额外的信息。

可以使用子父类实例的地方,也可以使用子类实例。

关联关系提供了通信的路径,它是所有关系中最通用的、语义最弱的。在 UML 中,用一条实线来表示关联关系。

聚合关系:聚合是一种特殊形式的关联。聚合表示类之间的关系是整体与部分的关系。例如一辆轿车包含四个车轮、一个方向盘、一个发动机和一个底盘,就是聚合的一个例子。在 UML中,用一个带空心菱形的实线表示,空心菱形指向的是代表“整体”的类。

组合关系:如果聚合关系中的表示“部分”的类的存在,与表示“整体”的类有着紧密的关系,例如“公司”与“部门”之间的关系,那么就应该使用“组合”关系来表示。在 UML 中,用带有实心菱形的实线表示,菱形指向的是代表“整体”的类。

希赛教育专家提示:聚合关系是指部分与整体的生命周期可以不相同,而组合关系则是指部分与整体的生命周期是相同的。

多重性是用来说明关联的两个类之间的数量关系的,例如:

书与借书记录之间的关系,就应该是 1 对 0…1 的关系,也就是一本书可以有 0 个或 1 个借书记录。

经理与员工之间的关系,则应为 1 对 0…*的关系,也就是一个经理可以领导 0 个或多个员工。

学生与选修课程之间的关系,就可以表示为 0…对 1…的关系,也就是一个学生可以选择1 门或多门课程,而一门课程可以有 0 个或多个学生选修。

5. 交互图基础

交互图是表示各组对象如何依某种行为进行协作的模型。通常可以使用一个交互图来表示和说明一个用例的行为。在 UML 中,包括 3 种不同形式的交互图,强调对象交互行为顺序的顺序图,强调对象协作的通信图(UML1.X 版本中称为“协作图”),强调消息的具体时间的定时图,它们之间没有什么本质不同,只是排版不尽相同而已。

如图 8-16 所示,顺序图存在两个轴,水平轴表示不同的对象,即图中的 Client、Factory、Product 等;而垂直轴表示时间,表示对象及类的生命周期。

对象间的通信通过在对象的生命线间画消息来表示。消息的箭头指明消息的类型。顺序图中的消息可以是信号、操作调用或类似于 C++中的 RPC(Remote Procedure Calls)和 Java中的 RMI(Remote Method Invocation)。当收到消息时,接收对象立即开始执行活动,即对象被激活了。通过在对象生命线上显示一个细长矩形框来表示激活。

消息可以用消息名及参数来标识,消息也可带有顺序号。消息还可带有条件表达式,表示分支或决定是否发送消息。如果用于表示分支,则每个分支是相互排斥的,即在某一时刻仅可发送分支中的一个消息。

根据 UML 的定义,定时图实际上是一种特殊形式的顺序图(即一种变化),它与顺序图的区别主要体现在以下几个方面。

坐标轴交换了位置,改为从左到右来表示时间的推移。

用生命线的“凹下凸起”来表示状态的变化,每个水平位置代表一种不同的状态,状态的顺序可以有意义、也可以没有意义。

生命线可以跟在一根线后面,在这根线上显示一些不同的状态值。

可以显示一个度量时间值的标尺,用刻度来表示时间间隔。

图 8-18 是一个定时图的实际例子,其中小黑点加曲线表示的是注释。它用来表示一个电子门禁系统的控制逻辑,该门禁系统包括门(物理的门)、智能读卡器(读取用户的智能卡信息)、处理器(用来处理是否开门的判断)。

在这个例子中,它所表示的意思是一开始读卡器是启用的(等用户来刷卡)、处理器是空闲的(没有验证的请求)、门是关的;接着,当用户刷卡时,读卡器就进入了“等待校验”

的状态,并发一个消息给处理器,处理器就进入了校验状态。如果校验通过,就发送一个“禁用”消息给读卡器(因为门开的时候,读卡器就可以不工作),使读卡器进入禁用状态;并且自己转入启用状态,这时门的状态变成了“开”。而门“开”了 30 秒钟(根据时间刻度得知)之后,处理器将会把它再次“关”上,并且发送一个“启用”消息给读卡器(门关了,读卡器开始重新工作),这时读卡器再次进入启用状态,而处理器已经又回到了空闲状态。

从上面的例子中,不难看出定时图所包含的图元并不多,主要包括生命线、状态、状态变迁、消息、时间刻度,可以根据自身的需要来使用它。

6. 状态图基础状态图

用来描述一个特定对象的所有可能状态及其引起状态转移的事件。大多数面向对象技术都用状态图表示单个对象在其生命周期中的行为。一个状态图包括一系列的状态及状态之间的转移。图 8-19 是一个数码冲印店的订单状态图实例。

状态图包括以下部分。

状态:又称为中间状态,用圆角矩形框表示;

初始状态:又称为初态,用一个黑色的实心圆圈表示,在一张状态图中只能够有一个初始状态;

结束状态:又称为终态,在黑色的实心圆圈外面套上一个空心圆,在一张状态图中可能有多个结束状态;

状态转移:用箭头说明状态的转移情况,并用文字说明引发这个状态变化的相应事件是什么。

一个状态也可能被细分为多个子状态,那么如果将这些子状态都描绘出来的话,那这个状态就是复合状态。

状态图适合用于表述在不同用例之间的对象行为,但并不适合用于表述包括若干用例协作的对象行为。通常不会需要对系统中的每一个类绘制相应的状态图,而通常会在业务流程、控制对象、用户界面的设计方面使用状态图。

7. 活动图基础

活动图的应用非常广泛,它既可用来描述操作(类的方法)的行为,也可以描述用例和对象内部的工作过程。活动图是由状态图变化而来的,它们各自用于不同的目的。活动图依据对象状态的变化来捕获动作(将要执行的工作或活动)与动作的结果。活动图中一个活动结束后将立即进入下一个活动(在状态图中状态的变迁可能需要事件的触发)。

如图 8-20 所示,活动图与状态图类似,包括了初始状态、终止状态,以及中间的活动状态,每个活动之间,也就是一种状态的变迁。在活动图中,还引入了以下几个概念。

判定:说明基于某些表达式的选择性路径,在 UML 中使用菱形表示。

分支与组合:由于活动图建模时,经常会遇到并发流,因此在 UML 中引入了如上图 8-20 所示的粗线来表示分支和组合。

发送和接收标识也可与对象相连,用于表示消息的发送者和接收者。

8. 构件图基础

构件图是面向对象系统的物理方面进行建模要用的两种图之一。它可以有效地显示一组构件,以及它们之间的关系。构件图中通常包括构件、接口及各种关系。图 8-22 就是一个构件图的例子。

通常构件指的是源代码文件、二进制代码文件和可执行文件等。而构件图就是用来显示编译、链接或执行时构件之间的依赖关系的。例如,在图 8-22 中,就是说明 QueryClient.exe将通过调用 QueryServer.exe 来完成相应的功能,而 QueryServer.exe 则需要 Find.exe 的支持, Find.exe 在实现时调用了 Query.dll。

通常来说,可以使用构件图完成以下工作。

对源代码进行建模:这样可以清晰地表示出各个不同源程序文件之间的关系。

对可执行体的发布建模:如图 8-22 所示,将清晰地表示出各个可执行文件、DLL 文件之间的关系。

对物理数据库建模:用来表示各种类型的数据库、表之间的关系。

对可调整的系统建模:例如对应用了负载均衡、故障恢复等系统的建模。

在绘制构件图时,应该注意侧重于描述系统的静态实现视图的一个方面,图形不要过于简化,应该为构件图取一个直观的名称,在绘制时避免产生线的交叉。

9. 部署图基础

部署图,也称为实施图,它和构件图一样,是面向对象系统的物理方面建模的两种图之一。构件图是说明构件之间的逻辑关系,而部署图则是在此基础上更进一步地描述系统硬件的物理拓扑结构及在此结构上执行的软件。部署图可以显示计算结点的拓扑结构和通信路径、结点上运行的软件构件,常用于帮助理解分布式系统。

图 8-23 就是与图 8-22 对应的部署图,这样的图示可以使系统的安装、部署更为简单。

在部署图中,通常包括以下一些关键的组成部分。

如图 8-23 所示,“客户端:个人 PC”和“服务器”就是两个节点。在 UML 中,使用一个立方体表示一个节点,节点名放在左上角。节点之间的连线表示系统之间进行交互的通信路径,在 UML 中被称为连接。通信类型则放在连接旁边的“《》”之间,表示所用的通信协议或网络类型。

逻辑上它可以与类图中的包或类对应。例如,在图 8-23 中,“服务器”结点中包含“QueryServer.exe”、“Find.exe”和“Query.dll”3 个构件。

在面向对象方法中,类和构件等元素并不是所有的属性和操作都对外可见。它们对外提供了可见操作和属性,称之为类和构件的接口。界面可以表示为一头是小圆圈的直线。在图8-23 中,“Query.dll”构件提供了一个“查询”接口。

8.5 用户界面设计

接口设计主要包括三个方面的内容:一是设计软件构件间的接口;二是设计模块和其他非人的信息生产者和消费者(如外部实体)的接口;三是人(如用户)和计算机间界面设计。

软件构件间接口的设计与架构的设计紧密相关,而设计模块和外部实体的接口则与详细设计相关,人机界面接口是相当容易被忽视的环节,在此就对其重点内容进行一个概要性描述。

8.5.1 用户界面设计的原则

用户界面设计必须考虑软件使用者的体力和脑力,根据 Theo Mandel 的总结,设计时必须遵从三个黄金法则。

置用户于控制之下:具体来说就是以不强迫用户进入不必要的或不希望的动作的方式来定义交互模式、提供灵活的交互、允许用户交互可以被中断和撤销、当技能级别增长时可以使交互流水化并允许定制交互、使用户隔离内部技术细节、设计应允许用户和出现在屏幕上的对象直接交互。

减少用户的记忆负担:具体来说就是减少对短期记忆的要求、建立有意义的默认、定义直觉性的捷径、界面的视觉布局应该基于对真实世界的隐喻、以不断进展的方式提示信息。

保持界面的一致:具体来说,就是允许用户将当前任务放入有意义的语境、在应用系列内保持一致性,如果过去的交互模型已经建立了用户期望,除非有不得已的理由,否则不要改变它。

除此之外,还应该考虑表 8-7 所示的设计原则。

8.5.2 用户界面设计过程

用户界面的设计过程也应该是迭代的,它通常包括 4 个不同的框架活动,如图 8-24所示。

8.6 工作流设计

工作流技术的发展,经过多年的努力,取得了一定的成果。但在实际应用中,应用的企业还是较少,应用的范围窄,效果不理想。

流程的设计是对设计者更高的挑战,现实中对计算机所管理的流程需要灵活的定义、方便的路径修改、容易使用,可是这几个目标是矛盾的。更严重的是,如何分析现实中的流程本身就是个困难的问题,更不用谈如何来设计实现了。流程设计的主要困难实际上也就是软件的主要困难:现实复杂性。

任何对现实的描述(图形也罢,文字也罢)都是不完美的,“不识庐山真面目”是设计面临的共同难题。设计者不得不意识到所有的流程模型都是对现实的简化,计算机只根据确定的信息作判断,而现实中的流程存在大量的不确定性,虽然计算机专家们自信地告诉企业管理者这是管理的问题,信誓旦旦地保证可以用计算机系统来“完善”企业的管理,但他们似乎没有意识到企业管理已经发展了几百年,而计算机还没有百年的历史。

人们常常抱怨计算机系统的流程设计太过刻板,因为许多时候,标准流程是先于应用构造的且由一些集中的权威强制执行的,所以这种刻板性是不可避免的。同时,对参与者而言缺乏自由度导致工作流管理系统显得很不友好。结果是它们经常被忽略或绕过,甚至最终被放弃。

另外的困难是:对于流程处理,不但名称众多,例如,动态模型、工作流等,而且对流程的定义也是千姿百态。面对这些困难,设计者无疑需要巨大的勇气来进行流程设计。

8.6.1 工作流设计概述

限于篇幅,这里只列出工作流管理联盟对于工作流的定义:“工作流是一类能够完全或者部分自动执行的经营过程,根据一系列过程规则、文档、信息或任务在不同的执行者之间传递、执行”。

这个过程包括了众多因素:任务顺序、路线规则、时间时限约束等。

要分析现实中的处理流程,必须首先描述目标系统的流程,这个过程也可以称为建模。

流程是个复杂的事务,必须从多方面才可以描述一个流程,包括:

“谁”,流程的参与者; “什么”,参与者做什么工作;“何时”,工作完成的时间限制,还需要说明工作的数据流和完成工作的控制流。人们认为自然语言在描述如此复杂的事务时容易引起歧义,所以人们定义了一些形式化语言试图在自然语言中挑选一个子集,这个子集既可以真正描述流程,又能够摆脱自然语言的复杂和多变,实际上想在人和机器在理解处理流程上架起一座桥梁,如同其他计算机语言及后来发展的统一建模语言一样,这些形式化的语言也称为“工作流定义语言”。

同样,为了描述实际中的处理流程,人们也想到了图形的方式。有限状态自动机是一种分析状态和改变状态的良好工具,这种方法需要完全列出流程中所有状态及这些状态的组合,当处理流程变得庞大时,自动机所对应的状态图膨胀得太厉害。由于 Petri 网有严格的数学基础和图形化的规范语义,在描述离散事件动态系统方面的能力已经得到公认,具有较强的模型分析能力,在工作流的描述和分析中已经是人们广泛采用的一种方法。虽然有人认为图形并非工作流的最佳表示方法,但由于图形的直观性,大多数设计者都愿意采用图形的描述方式。有关有限状态自动机和 Petri 网的论述请参考其他书籍。

8.6.2 工作流管理系统

根据工作流管理联盟(Workflow Management Coalition,WFMC)的定义,工作流管理系统是一种“在工作流形式化表示的驱动下,通过软件的执行而完成工作流定义、管理及执行的系统”,其主要目标是对业务过程中各活动发生的先后次序及与活动相关的相应人力或信息资源的调用进行管理,而实现业务过程的自动化。

如同关系数据库一样,现在已经出现了专门的工作流管理系统,这些系统经过专门的设计,从不同的角度负责解决设计者在流程设计中遇到的共同问题:节点定义、路径选择、数据流动等。不幸的是,和关系数据库共同基于关系代数、支持标准的 SQL 不同,这些工作流管理系统基于不同的数学模型,提供完全不同的接口。所以这些工作流管理系统各具特色,但在通用性和其他系统相互协作上的不足使得这些系统的应用受到了限制。

WFMC 给出了包含六个基本模块的参考模型,这六个模块被认为是工作流管理系统的最基本组成,这个参考模型同时包括了这些模块之间的接口标准。

这就需要这些工作流管理系统之间进行有效的交互,避免画地为牢、信息孤岛的现象出现。

看到这里,没有处理流程设计经验的设计者一定已经云里雾里了。确实,流程设计是系统设计中最困难的一部分,它的复杂性直接来源于现实世界的复杂性。而且直到现在,人们对流程的设计,仍然是在探索之中。

8.7 简单分布式计算机应用系统的设计

网络极大地扩展了计算机的应用范围,同时,由于升级到更强的服务器的费用常常远远高于购买多台档次稍低的机器,更何况虽然计算机有了长足的发展,可是单台计算机的功能仍然十分有限,利用联网的计算机协同工作,共同完成复杂的工作成为相对成本较低的选择,而且可以完成单台计算机所无法完成的任务。分布式系统使得这一目标成为可能。另外,网络本质上并不可靠,特别是远程通信,分布式系统还带来了并发和同步的问题。

分布式系统可以由两种完全不同的方式来进行协同和合作,第一种是基于实例的协作。

这种方式所有的实例都处理自己范围内的数据,这些对象实例的地位是相同的,当一个对象实例必须要处理不属于它自己范围的数据时,它必须和数据归宿的对象实例通信,请求另外一个对象实例进行处理。请求对象实例可以启动对象、调用远程对象的方法,以及停止运行远程实例。基于实例的协作具有良好的灵活性,但由于实例之间的紧密联系复杂的交互模型,使得开发成本提高,而且,由于实例必须能够通过网络找到,所以通信协议必须包括实例的生存周期管理,这使得基于实例的协作大多只限于统一的网络,对于复杂的跨平台的系统就难以应付。所以基于实例的协作适用于比较小范围内网络情况良好的环境中,这种环境常常被称为近连接。这种情况下对对象的生存周期管理所带来不寻常的网络流量是可以容忍的。

使用基于实例的协作常常使用被称为“代理”的方法,某个对象实例需要调用远程对象时,它可以只和代理打交道,由代理完成和远程对象实例的通信工作:创建远程对象,提交请求、得到结果,然后把结果提交给调用的对象实例。这样,这个对象实例甚至可以不知道自己使用了远程对象。当远程对象被替换掉(升级)时,对本地代码也没有什么需要修改的地方。

另外一种方式是基于服务的协作,该方法试图解决基于实例的协作的困难。它只提供远程对象的接口,用户可以调用这些方法,却无法远程创建和销毁远程对象实例。这样减少了交互,简化了编程,而且使得跨平台协作成为可能。同样由于只提供接口,这种协作方式使得对象间的会话状态难以确定,而且通信的数据类型也将有所限制,基本上很难使用自定义的类型。基于服务的协作适用于跨平台的网络,网络响应较慢的情况,这种环境常称为远连接,这时,简化交互性更为重要,而频繁的网络交换数据会带来难以容忍的延时。

基于服务的协作往往采用分层次的结构,高层次的应用依赖于低层次的对象,而低层次对象实例的实现细节则没有必要暴露给高层次对象,这种安排使得高层次的实现不受低层次如何实现的影响,同时,当低层次服务修改时,高层次的服务也不应该受到影响。

设计者在进行设计时,通常会倾向于比较细致的设计,对象往往提供了大量的操作和方法,响应许多不同的消息,以增加达到系统的灵活性、可维护性等。这在单个系统中没有什么问题,当考虑分布式系统的设计时,这种细致的设计所带来对对象方法的大量调用会比较严重地影响性能,所以在分布式系统中,倾向于使用大粒度的设计方式,往往在一个方法中包含了许多参数,每个方法基本上代表了一个独立的功能。当然这样的设计使得参数的传递变得复杂,当需要修改参数时,需要对比较大范围的一段过程代码进行修改,而不是像小粒度设计一样,只需要修改少量的代码。

8.8 系统运行环境的集成与设计

在设计一个新的系统时,设计者必须考虑目标系统的运行环境问题,人们往往认为软件应该能够在任何环境中运行,常常看到这样的系统,硬件已经升级了多次,而软件还是原来的软件。软件的运行环境是指系统运行的设备、操作系统和网络配置。

本节给出软件运行的几个典型环境,设计者可以从这几种典型环境中选择适合自己的目标系统的环境,也可以将这些典型环境做一些组合,来满足自己设计的系统的特殊要求。

1. 集中式系统

早期的计算机系统没有什么可以选择的,除了集中式系统。所有的操作都集中于一台主机中,而操作员必须在主机的附近操作,结果也在附近给出。这种系统仍然广泛地应用于批处理应用系统及更大的分布式系统的一部分。集中式系统常见于银行、保险、证券行业,它们含有大规模的处理应用。而现在流行的电子商务又给大型处理机注入了新的活力,人们发现电子商务要面对大量的事务,需要大型处理机来处理。但是,实践中很少单独使用集中式系统,因为大量的系统需要处理在地理上分布得很远的连接请求,这些请求有的需要实时响应,并可能要发送到其他某个地方的一个集中式系统。所以,在现代的系统中,集中式系统通常是某个分布式系统的一个环节。

集中式系统由以下几个部分组成。

通常采用负载均衡、资源共享等方式实现更大的处理能力和容量。

2. 分布式系统

在 7.9 节中,已经简单介绍了分布式系统。分布式系统由于网络的普遍延伸,费用的不断降低而越来越成为大型系统的首选环境。分布式系统必须基于网络,这个网络可以是在一个地域内的局域网,也可以是跨越不同城市乃至国家的广域网。对比集中式的计算机环境,分布式系统有着多种多样的形式。这也给设计者在确定系统运行环境时带来一定的烦恼。

3. C/S 结构

系统由提供服务的服务器和发起请求、接受结果的客户机构成。这种结构是一种可以使用很多方式实现的通用结构模型。并非只限于数据库的 C/S 结构,典型的还有网络打印服务系统,现在流行的网络游戏也显然是基于这种结构的。

4. 多层结构

这种结构是 C/S 结构的扩展,典型的分为由存储数据的数据库服务器作为数据层、实现商业规则的程序作为逻辑层、管理用户输入输出的视图层所组成的三层结构。当系统更复杂时,可以再增加其他层次构成多层结构。

多层结构形式复杂,功能多样。实现多层结构常常需要来实现不同层次间通信的专门程序——管件,也称为中间件。中间件大多数实现远程程序调用、对象请求调度等功能。

现代企业级的计算机系统大量地基于分布式结构。支持分布式系统的软件也曾经如同雨后春笋。系统如何分层、如何处理分布带来的同步等问题也就同样在考验设计者。

5.Internet、Intranet 和 Extranet

Internet 是全球的网络集合,使用通用的 TCP/IP 协议来相互连接。Internet 提供电子邮件、文件传输、远程登录等服务。Intranet 是私有网络,只限于内部使用,也使用 TCP/IP协议。Extranet 是一个扩展的 Intranet。它包括企业之外的和企业密切相关合作的其他企业。

Extranet 允许分离的组织交换信息并进行合作,这样就形成了一个虚拟组织。现在的 VPN技术允许在公用网络上架构只对组织内部开发服务。

Web 同样基于 C/S 结构,实际上 Web 接口是一个通用的接口,不是只能使用浏览器的协议,它同样能够在普通的程序中使用。Internet 和 Web 已经给设计者提供了一个非常富有吸引力的选择方案。它的优势在于:它们已经成为网络的事实上的标准,支持它们的软件已经广泛地存在于全世界的计算机中,而且通信费用已经下降到很有竞争力的水平。从某种程度上来说,企业可以把 Internet 当作自己廉价的广域网。没有它们,电子商务还是水中月。

当然,事物有相反的一面,当设计者试图采用 Internet 时,必须考虑其不利的一面。

Internet 的安全性过去是,现在是,以后仍然是设计者头痛的问题。其他诸如可靠性、系统吞吐量、不断发展的技术和标准都是影响系统选择它们作为运行环境的不利因素。设计者应该根据目标系统的实际需要来选择不同的运行环境。不过,已经有越来越多的公司提供支持 Internet 和电子商务的接口。

8.9 系统过渡计划

当新系统似乎开发完毕,要取代原来的系统时,系统过渡就是设计者不得不面对的问题。

这个问题,不幸的是,比许多人想象得要复杂,和软件开发一样,存在着许多冲突和限制。

例如,费用、客户关系、后勤保证和风险等。设计者需要考虑的问题也很多,其中比较重要的几个问题是:

如果同时运行两个系统,会给客户造成多大的开销;

如果直接运行新系统,客户面对的风险有多大;

对新系统试运行时的查错和纠错,以及出现严重错误而导致停止运行时的应急措施;

客户运行新系统将面临的不利因素有哪些;

人员的培训。

使用不同的系统过渡方案意味着不同的风险,不同的费用及不同的复杂度。

1. 直接过渡

这是一种快速的系统过渡方式,当新系统运行时,立即关闭原来的系统。这种过渡方式非常简单,没有后勤保障的问题,也不要消耗很多资源。同时,它也意味着大风险,目标系统的特性决定了风险的大小。设计者主要要权衡当新系统失败时,系统停止运行或者勉强运行给客户带来的损失有多大。由于这种过渡方式简单而费用低廉,对于可以容忍停机一段时间的系统的实践者,可以采用这种方式。

2. 并行过渡

设计者采用并行过渡方式,让新系统和旧系统在一段时间里同时运行,通过这样的旧系统作为新系统的备份,可以大大降低系统过渡的风险。可是并行过渡显然比直接过渡要消耗更多的资源:现有的硬件资源必须保证能同时跑两套系统,这常常意味着增加服务器和额外的存储空间,需要增加人员来同时使用两套系统,或者增加现有员工的工作量,让他们同时操作两套系统。这种方式同时也增加了管理和后勤保障的复杂度。据统计,并行过渡时期的开销是旧系统单独运行时的 2.5~3 倍。

设计者还会发现有些系统无法使用并行过渡的方式,主要是客户没有足够的资源来维持两个系统同时运行,另外一种情况是新、旧系统差别太大,旧系统的数据无法为新系统采用。

当客户无法使用并行过渡,又想尽可能地减少风险,设计者可以使用部分并行过渡的策略,使并行的开销减少到客户能够接受的范围内。

3. 阶段过渡

通常在系统非常复杂、过于庞大以至于无法一次性进行过渡时采用,也适用于分阶段开发的系统。设计者需要设计一系列步骤和过程来完成整个系统的过渡,这种过渡方式和系统的复杂程度相关,随着系统的不同往往有很大的不同。和并行过渡一样,阶段过渡也能够减少风险,显然局部的失败要比全体的失败更容易接受,带来的损失更小。阶段过渡也带来了复杂性,有时候比并行过渡更加复杂。