一 UML概述

1 基本概念

UML(统一建模语言,Unified Modeling Language)是OMG(Object Management Group)组织在1997年发表的图标式软件设计语言,是一个绘制软件概念图的图形化记法(notation)。人们可以用它绘制图形,用这些图形来表示一个计划进行的软件设计的问题域,或者用这些图来表示一个已经完成的软件实现。

UML综合了当时很多种已存在的面向对象的建模语言、方法和过程,主要包括:

Booch Method

Object-Oriented Software Engineening

Schlaer-Mellor

Coad-Yourdon

Object Modeing Technique

UML可分为三个种不同的层次:概念层(Conceptual)、规格说明层(Specification)和实现层(Implementation)

概念层上的图形与源代码没有什么严格的关系,它们与人类自然语言相关。它们是用来描述有关已经存在的人类的问题领域的概念和抽象的速记。它们无须遵从严格的语义规则,因此它们的意思理解会有歧义、主题可被解释。

规格说明层和实现层的图形与源代码有明显的关系,实际上,规格说明层的图是准备用来转换成成源代码的,类似地,实现层的图是打算用来描述已经存在的源代码的。在这些层次的图形,有许多规则和语义学要遵从,这些图较少有歧义,基本上都有严格的格式。

举例:一条狗(Dog)是一只动物(Animal)。

表示这句话的一个概念层次的UML图如下

这个图描绘了通过泛化(generalization)关系连接起来的称为Animal(动物)和Dog(狗)的两个实体。Animal是Dog的泛化,一条Dog是一种特定的Animal。这是所有这张图的意义了,没有什么其他意思可以从中推断出来了。这个概念模型没有涉及任何有关计算机、数据处理和程序。我们可以声称,我们的宠物狗是一只动物,我们或者可以谈到属于动物界的生物学的分类上去。因此,这张图是主题可解释的。

不过,这张图在规格说明层次和实现层次上有更明确的意思:

这些代码定义了通过继承关系连接的Animal类和Dog类,这个规格说明模型描述了程序的一部分。

一个概念层次上的图没有定义源代码,也不应该去定义源代码。一个描述了某个问题解决方法的规格说明层次的图,也不会去寻找任何像概念层那样的问题的描述。

2 UML的功能

对一个软件系统来说,UML具有以下主要功能[BOOCH99]:可视化功能;说明功能;建造功能和文档化功能。

可视化(Visualizing)功能

这是非常有价值的,从一个可视化的图上去评估一个系统的依存结构比从代码中去评估容易多了。

可视化可以促进对问题的理解,并且方便设计师彼此交流和沟通。

可以比较容易的发现设计图中可能存在的逻辑错误,避免和减少意外发生。

说明(Specifying)功能

提供了一种通用的,精确的,没有歧义的机制,来对一个软件系统进行说明。

建造(Costructing)功能

UML提供了自己的标准语法规则,可以使用建模工具软件对一个系统设计模型进行解释,并将设计模型映射到计算机语言(如Java)上。也就是说,可以加快系统的设计,实现过程。

通过UML可以反映系统的总貌。这样,当系统设计首先完成后,可以比较容易的发现可以复用的部分,从而降低开发成本。

文档化(Documenting)功能

使用UML进行设计可以同时产生系统设计文档。文档可以帮助开发人员更快的熟悉系统,节省学习时间。


二 UML建模工具

有很多工具可以用来画UML图形。其中有些是纯粹的图形工具,有些则是具有代码生成功能的OO设计工具。包括:

ü  Rational ROSE

ü  Together

ü  Micrisoft Viso

ü  Visual UML

ü  纸和白板


三 为什么使用软件模型?

1 为什么使用模型?

问题:搭狗窝和建造纽约世贸中心

为什么工程师要建造模型(models)?为什么航天工程师要建造航天器的模型?为什么桥梁工程师要建造桥的模型?提供这些模型的目的是什么?

提供标准的沟通方式

设计图纸是设计的语言,是工程设计人员,施工人员之间沟通的语言。在一个现代化的工程里,人们要相互沟通和合作,就必须使用标准的工业化设计语言。

通过建立模型来验证事物

工程师建造模型来查明他们的设计是否可以正常工作。航天工程师建造好了航天器的模型,然后把他们放入风洞中了解这些航天器是否可以飞行。桥梁工程师建造桥的模型来了解桥能否接立起来。建筑工程师建造建筑的模型了解客户是否喜欢这种建筑模样。

模型必须是可被检验的。为了检验它,如果一个模型没有一个可用的检验标准,它是相当糟糕的。如果你不能评估一个模型,这个模型是没有价值的。

成本

为什么航天工程师不马上建造一个飞机然后去试飞呢?为什么桥梁工程师不立即建造一座桥然后看它是否可以接立起来呢?因为航天器和桥的造价比模型昂贵多了。当模型比我们实际建造的东西划算多了的时候,我们用模型来研究设计。

2 为什么使用软件建模?

一个UML图可被检验吗?它比创建、检验这个软件更划算吗?

对于这两个问题,这个答案是无法像航天工程师和桥梁工程师那样清楚地了解境况。没有一个检验一个UML图的固定标准。我们能够观察它、评估它,然后应用原则和模式于它,但是最后的评估仍然是相当主观的。画UML图比编写软件花费更少,但不是重要的因素。当然,改变一个UML图比修改源代码容易多了,用UML是不是有意义呢?

当我们需要通过检验确定某些东西的时候,或是使用UML来检验比编码来检验更划算的时候,我们就使用UML。

举一个例子,我有一个特定设计的主意,我需要通过我的团队中的开发人员来考虑它是不是一个好主意去检验,因此,我在白板上画出了一个UML图,然后询问队友们的反馈。

3 是否应该在编码前构造一个全面的设计?

桥梁工程师、航天工程师和建筑工程师都画设计图,为什么呢?因为画一个房子的设计图一个人就可以了,而建造它需要五个或更多人。区区十来个航天工程师能画一个飞机的设计图,而需要上千人去建造它。绘制设计图不需挖掘地基、浇注混凝土和嵌上窗户。简而言之,预先计划一个建筑物远比没有计划的情况下试图建筑它更划算。丢弃一张有错误的设计图花不了多少钱,而拆卸一栋失败的建筑物却要花不少的钱。

软件开发是否应该在编码前构造一个全面的设计?不一定。实际上,许多项目团队在UML图上花费了比编写代码本身更多的时间。弃用一个图比弃用代码是不是更划算,这也不一定。因此,在编写代码前去创建一个全面的UML设计作为一个有价值、有效的选项,也是不一定的。

四 各种UML图形

根据UML图形的用途,可以划分为三类。

静态图(static diagrams)描述了那些不发生变化的软件元素的逻辑结构,描绘了类、对象、数据结构及其存在于它们之间的关系。

动态图(Dynamic diagrams)展示了在运行期间的软件实体的变化,描绘了执行流程、实体改变状态的方式。

物理图(Physical diagrams)显示了软件实体的不变化的物理结构,描绘的物理实体有源文件、库文件、字节文件、数据文件等等,以及存在于它们之间的关系。

类型

名称

描述

静态图

(static diagrams)

类图

(Class Diagram)

描述类的结构及类之间的静态关系

对象图

(Object Diagram)

给出系统中对象的快照

动态图

(Dynamic diagrams)

用例图

(UseCase Diagram)

描述一系列的角色和用例间的关系

活动图

(Activity Diagram)

描述不同过程间的动态接触

序列图

(Sequence Diagram)

描述不同对象间信息传递的顺序

状态图

(State Diagram)

描述对象的内部状态及状态的转移

协作图

(Collaboration Diagram)

描述发出信息,接受信息的一系列对象的组织结构

物理图

(Physical diagrams)

构件图

(Component Diagram)

描述可部署的软件构件(如jar文件)之间的静态关系

部署图

(Deployment Diagram)

描述一个系统的拓扑结构

通常,最常用的图形是类图(Class Diagram),用例图(UseCase Diagram),状态图(State Diagram)和序列图(Sequence Diagram)。

1 静态图(static diagrams)

描述了那些不发生变化的软件元素的逻辑结构,描绘了类、对象、数据结构及其存在于它们之间的关系。包括:

类图(Class Diagram)

 

在一个类图中,我们能够查看一个类的属性和方法。我们也能查看一个类是否继承自另外一个类,是否拥有对另外一个类的引用。简而言之,我们能够描绘出类之间的依存关系。

对象图(Object Diagram)

有些时候,将系统在某一特定时刻的状态表示出来是很有用的,特别是当系统结构是动态建立而不是由它类的静态结构表示时。UML对象图就像系统运行时的一个快照(Snapshot),显示了给定实例的对象、关系和属性值。

举例:一个建筑平面设计软件。可以使用GUI画建筑物的平面布置图。程序记录了房间、门、窗户和开了口子的墙,其系统结构下图所示。

这个图能够说明可能会使用哪些类型的数据结构,但是无法告诉你在某个特定的运行时刻有什么对象和关系被实例化了。

设想一下:一个用户使用软件画了两个房间,一个厨房、一个餐厅,它们之间用一堵墙相连着。厨房和餐厅各有一个向外的窗户,餐厅还有一个向外开的门,门是打开着的。这个场景就可以用如下对象图进行描叙。

显示了当时存在于系统中的对象,以及它们被哪些对象关联着。它显示厨房和餐厅是 Space 类的两个独立实例;显示了这两个房间是如何通过墙相连;显示外部空间 实际上是用 Space 类另外一个实例表示;还显示了所有其他必须存在的对象和关系。

需要注意的是不要滥用对象图。当需要对象图时,它们是必不可少的。但是通常,并不是经常需要它们,很多对象图能够由类图直接推断出来,因此它们应用得比较少。为系统的每个场景,甚至每个系统都画对象图是件不可想象的事情!

2 动态图(Dynamic diagrams)

展示了在运行期间的软件实体的变化,描绘了执行流程、实体改变状态的方式。包括:

用例图(UseCase Diagram)

一个用例是有关一个系统的行为的一个描述。描述是从一个用户的观点编写的,这个用户使用系统去做一些特定的事情。一个用例捕获一个事件的可视化序列,这个事件是一个系统对单个用户的激励的响应过程。一个可视化事件是这个用户可以看到的事件,用例不描述任何隐藏着的行为,它们不讨论有关系统的隐藏着的机制,它们仅描述那些用户可以看到的事情。

活动图(Activity Diagram)

 

序列图(Sequence Diagram)

序列图的有三个基本元素:对象、生命线、消息

上面展示了一个典型的序列图,用于描述一个软件系统登录的过程。有关协作的对象被放置在图的顶部,这个人样图标在左边表示一个匿名对象(参与者),它是所有进入和离开这个协作的消息的源头和汇集点。不是所有的序列图都有这样的匿名参与者,但大部分的图都有。

这条垂立在对象或参与者下面的虚线叫做生命线(lifelines)。从一个对象被发送到另外一个对象的消息被画成一个两条生命线之间的箭头。每个消息都被标记出名称。参数被画在消息名称后面的括号里或是一个数据标记(data tokens,一个以小圈结束的箭头)之后。时间(Time)是在垂直方向,因此越是下面的消息是越晚被发送。在 LoginServlet 对象生命线上的小框框叫做活动(Activation),活动是可选的,大多数图都不需要它们。它们表示函数的执行时间。在这个例子中表示 login 函数执行时间长短。 离开这个活动往右的两个消息被 login 方法发送出。那个没有标记的箭头表示 login 函数返回给参与者,传回一个返回值。 注意在 getEmployee消息中的返回变量 e,它表示 getEmployee 的返回值。注意这个 Employee 对象也叫做 e,它们是同一个东西。getEmployee 的返回值是一个Employee 对象的引用。 最后,注意那个 EmployeeDB 是一个类,而不是一个对象,这说明 getEmployee 是一个静态方法。

使用序列图时需要注意:

(1)保持简单

序列图可以展示相当复杂的逻辑,例如下图所示,一个按时付费系统。

不要画这种充斥着大量对象和消息返回的序列图!读懂它们很困难,因此很可能没有人去读,那是极大的时间浪费。相反,应该画一个更小的、能够抓住了你试图做的东西的本质的序列图。每张序列图应该只有一张纸大,留下大量的地方去用文本解释清楚。

(2)不要多画

千万为每一个类的每一个方法建立序列图!这样非常浪费你的时间。

不要画太多的序列图,如果你画得太多,将没有人去看它们。找出相关场景的共性,并将焦点放在这里。对于UML图来说,共性远比差异重要。用你的图去揭示共有的主题和共有的惯例。不要用它们去描述每个小细节。如果你真的需要画一个序列图去描述消息流,节制而简洁地使用它们,尽可能地少用。有时可能根本不需要去画序列图,也许代码能很好的、足够地说明它自己。有代码足够说明它自己时,图是多余的、浪费的。

代码真是能够用于描述系统的一部分吗?实际上,这应当是每个设计者和开发者的目标。团队应该努力去创建表述清楚和可读性强的代码,使更多的代码能够描述自己,更少地需要图。

(3)分割复杂场景

如果你感觉一个真的需要复杂的序列图,看看是否有办法把它们分割成几个小的场景,使其容易理解。

如下图所示,把一个大序列图分解成了更好读的更小的序列图。

(4)使用高层视图

通常,高层视图比低层视图更有用,它们将帮助读者更好地理解系统,它们揭示的共性比差异多。

前面的序列图展现了如何计算一个按时付费的低层操作细节,下图展现了一个系统流程的高层视图。

状态图(State Diagram)

状态图又称做状态转换图(State Transition Diagram)。对象被外界事件激发,从而从一个状态转变为另一个状态。状态图的基本想法是定义一个具有有限个内部状态的机器,因此又称做有限状态机(FSM)。

下图显示了一个简单的状态转换图,描述了如何通过有限状态机控制一个用户登录到一个系统。

图中的矩形(被分为上下两格)表示的是状态。每一个状态的名字显示在在状态图上面的框格中。在状态图的下面的框格中,是当进入和退出这个状态时出发的动作。例如:当我们 开始进入“Prompting for Login”这个状态的时候,我们调用“showLoginScreen”动作;当我们退出“Prompting for Login”这个状态的时候,调用“hideLoginScreen”这个 动作。

箭头表示的是状态之间的转换, 箭头被标以触发转换的事件的名称。当转换被触发的时候,有些箭头还标以要执行的动作。例如:假如我在“Prompting for Login”状态中,并且获得了“login”这个事件,那么我们将转换到“ Validating User”状态,并调用“validateUser”这个动作。

一个实心的黑色圆圈,它叫做初始伪状态(initial pseudo state)。有限状态机的生命周期,是从这个初始状态开始的。 因而,这个例子是从“Prompting for Login”这个状态开始进行转换的。

图中有一个超状态(super state),里面包括了“Sending Password Failed”和“Sending Password Succeeded”两个状态。这两个状态都是通过OK 事件而转换到“Prompting for Login”状态的箭头,所以使用了更简单的一个超状态进行描述。

通过这个有限状态机图示,使登录这个功能很清晰明了,它将这个过程分成一些小的功能。如果实现图中的所有动作,并按照图中的逻辑组织起来,就可以确信这个 Login 的过程是可以工作的。

协作图(Collaboration Diagram)

3 物理图(Physical diagrams)

显示了软件实体的不变化的物理结构,描绘的物理实体有源文件、库文件、字节文件、数据文件等等,以及存在于它们之间的关系。包括:

构建图(Component Diagram)

部署图(Deployment Diagram)


五 类图

1 类的表示

下图展现了一个类图的最简单的形式。

这是表现一个类的最常用的方法,大多数类图中类有一个能够清楚表达的命名就可以了。

下图中展现了一个类的部分细节。

一个类的图像符号被细分成几层。

最上面部分表示类的名字。在类图中,类名层是不能省略的,其他几层都可以在UML图中省略。

第二层表示类的属性。类的属性可以是私有的(private),受保护的(protected)或公开的(public)。

第三层表示类的方法。类的方法可以是私有的(private),受保护的(protected)或公开的(public)。一个方法包括:方法名,参数名和数据类型,

紧接在属性或参数名称的冒号(:)号之后,表示了变量的类型或一个函数的参数的类型。同样地,函数的返回值的类型是在函数后面的冒号之后反映的。这种细节有时有用,但是用得并不是特别多。UML图并不是声明变量和函数的地方,这种事情最好在源代码里去做。

2 类的关系

在类与类之间,通过连线指明它们之间的关系。在类与类之间,类与接口之间可以建立的关系包括:关联关系,泛化关系,聚合关系,组合关系和依赖关系。

关联关系(Association)

关联关系表示类与类之间的连接,使一个类知道另一个类的属性和方法。关联可以是单向的,也可以是双向的。单向关联更为普遍,通常不推荐使用双向关联。

在Java语言里,关联关系是使用实例变量来实现的。

关联可以有一个名字。

在关联的端点,还可以有一个基数,表示类关联的多重性。

泛化关系(Generaliation)

泛化关系表示类与类之间的继承关系,类对接口的实现关系。泛化关系的连线是从子类指向父类,或从类指向被实现的接口,如下图所示

在Java语言里,泛化关系使用关键字extends 和 implements。

聚合关系(Aggregation)

聚合关系是关联关系的一种,是强的关联关系。聚合是特指整体和个体之间的关系。例如汽车类和引擎类,轮胎类之间的关系,如下图所示

与关联关系一样,在Java语言里聚合关系也是使用实例变量来实现的。但是,关联关系所涉及的两个类是处于同一层次上的,而在聚合关系中则不是处于同一层次上,一个代表整体,另一个代表部分。

关联关系和聚合关系仅从Java语法是分辨不出的,需要考察类之间的逻辑关系。

组合关系(Composition)

组合关系是关联关系的一种,是比聚合关系更强的关联关系。组合关系要求代表整体的对象负责代表部分的对象的生命周期,组合关系是不能共享的。

在下图中,显示了美猴王(MonkeyKing)和他的四肢(Limb)以及金箍棒(GoldStaff)之间的关系。

依赖关系(Dependency)

依赖关系表示一个类依赖于另一个类的定义。例如,一个人(Person)可以买车(Car)和房子(House),Person类依赖于Car类和House类,如下图所示。

在上例中,与关联关系不同的是,Person类里面并没有Car和House类型的属性,Car和House的实例是以参数的方式传入buy()方法中的。

在Java语言里,依赖关系体现为局域变量,方法的参数,以及对静态方法的调用。

如果类A有一个类B类型的属性,那么类A和类B就超出了依赖关系,而变成了某种关联关系。

六 用例分析技术(UseCase)

用例(Use Case)是一种描述系统需求的方法,使用用例的方法来描述系统需求的过程就是用例建模。用例方法最早是由Iva Jackboson博士提出的,后来被综合到UML规范之中,成为一种标准化的需求表述体系。用例的使用在RUP中被推崇备至,整个RUP流程都被称作是"用例驱动"(Use-Case Driven)的,各种类型的开发活动包括项目管理、分析设计、测试、实现等都是以系统用例为主要输入工件,用例模型奠定了整个系统软件开发的基础。

1. 什么是用例?

首先来看一下传统的需求表述方式-"软件需求规约"(Software Requirement Specification)。传统的软件需求规约基本上采用的是功能分解的方式来描述系统功能,在这种表述方式中,系统功能被分解到各个系统功能模块中,我们通过描述细分的系统模块的功能来达到描述整个系统功能的目的。一个典型的软件需求规约可能具有以下形式:

采用这种方法来描述系统需求,非常容易混淆需求和设计的界限,这样的表述实际上已经包含了部分的设计在内。由此常常导致这样的迷惑:系统需求应该详细到何种程度?一个极端就是需求可以详细到概要设计,因为这样的需求表述既包含了外部需求也包含了内部设计。在有些公司的开发流程中,这种需求被称为"内部需求",而对应于用户的原始要求则被称之为"外部需求"。

功能分解方法的另一个缺点是这种方法分割了各项系统功能的应用环境,从各项功能项入手,你很难了解到这些功能项是如何相互关联来实现一个完成的系统服务的。所以在传统的SRS文档中,我们往往需要另外一些章节来描述系统的整体结构及各部分之间的相互关联,这些内容使得SRS需求更象是一个设计文档。

参与者和用例

从用户的角度来看,他们并不想了解系统的内部结构和设计,他们所关心的是系统所能提供的服务,也就是被开发出来的系统将是如何被使用的,这就用例方法的基本思想。用例模型主要由以下模型元素构成:

这三种模型元素在UML中的表述如下图所示。

以银行自动提款机(ATM)为例,它的主要功能可以由下面的用例图来表示。ATM的主要使用者是银行客户,客户主要使用自动提款机来进行银行帐户的查询、提款和转帐交易。

通讯关联表示的是参与者和用例之间的关系,箭头表示在这一关系中哪一方是对话的主动发起者,箭头所指方是对话的被动接受者;如果你不想强调对话中的主动与被动关系,可以使用不带箭头的关联实线。在参与者和用例之间的信息流不是由通讯关联来表示的,该信息流是缺省存在的(用例本身描述的就是参与者和系统之间的对话),并且信息流向是双向的,它与通讯关联箭头所指的方向亳无关系。

用例的内容

用例图使我们对系统的功能有了一个整体的认知,我们可以知道有哪些参与者会与系统发生交互,每一个参与者需要系统为它提供什么样的服务。用例描述的是参与者与系统之间的对话,但是这个对话的细节并没有在用例图中表述出来,针对每一个用例我们可以用事件流来描述这一对话的细节内容。如在ATM系统中的"提款"用例可以用事件流表述如下:

提款-基本事件流

1. 用户插入信用卡

2. 输入密码

3. 输入提款金额

4. 提取现金

5. 退出系统,取回信用卡

但是这只描述了提款用例中最顺利的一种情况,作为一个实用的系统,我们还必须考虑可能发生的各种其他情况,如信用卡无效、输入密码错、用户帐号中的现金余额不够等,所有这些可能发生的各种情况(包括正常的和异常的)被称之为用例的场景(Scenario),场景也被称作是用例的实例(Instance)。在用例的各种场景中,最常见的场景是用基本流(Basic Flow)来描述的,其他的场景则是用备选流(Alternative Flow)来描述。对于ATM系统中的"提款"用例,我们可以得到如下一些备选流:

提款-备选事件流

备选流一:用户可以在基本流中的任何一步选择退出,转至基本流步骤5。

备选流二:在基本流步骤1中,用户插入无效信用卡,系统显示错误并退出信用卡,用例结束。

备选流三:在基本流步骤2中,用户输入错误密码,系统显示错误并提示用户重新输入密码,重新回到基本流步骤2;三次输入密码错误后,信用卡被系统没收,用例结束。

通过基本流与备选流的组合,就可以将用例所有可能发生的各种场景全部描述清楚。我们在描述用例的事件流的时候,就是要尽可能地将所有可能的场景都描述出来,以保证需求的完备性。

用例方法的优点

用例方法完全是站在用户的角度上(从系统的外部)来描述系统的功能的。在用例方法中,我们把被定义系统看作是一个黑箱,我们并不关心系统内部是如何完成它所提供的功能的。用例方法首先描述了被定义系统有哪些外部使用者(抽象成为Actor),这些使用者与被定义系统发生交互;针对每一参与者,用例方法又描述了系统为这些参与者提供了什么样的服务(抽象成为Use Case),或者说系统是如何被这些参与者使用的。所以从用例图中,我们可以得到对于被定义系统的一个总体印象。

与传统的功能分解方式相比,用例方法完全是从外部来定义系统的功能,它把需求与设计完全分离开来。在面向对象的分析设计方法中,用例模型主要用于表述系统的功能性需求,系统的设计主要由对象模型来记录表述。另外,用例定义了系统功能的使用环境与上下文,每一个用例描述的是一个完整的系统服务。用例方法比传统的SRS更易于被用户所理解,它可以作为开发人员和用户之间针对系统需求进行沟通的一个有效手段。

在RUP中,用例被作为整个软件开发流程的基础,很多类型的开发活动都把用例作为一个主要的输入工件(Artifact),如项目管理、分析设计、测试等。根据用例来对目标系统进行测试,可以根据用例中所描述的环境和上下文来完整地测试一个系统服务,可以根据用例的各个场景(Scenario)来设计测试用例,完全地测试用例的各种场景可以保证测试的完备性。

2. 建立用例模型

使用用例的方法来描述系统的功能需求的过程就是用例建模,用例模型主要包括以下两部分内容:

  • 用例图(Use Case Diagram)
  • 确定系统中所包含的参与者、用例和两者之间的对应关系,用例图描述的是关于系统功能的一个概述。
  • 用例规约(Use Case Specification)
  • 针对每一个用例都应该有一个用例规约文档与之相对应,该文档描述用例的细节内容。

在用例建模的过程中,建议的步聚是先找出参与者,再根据参与者确定每个参与者相关的用例,最后再细化每一个用例的用例规约。

(1)寻找参与者

所谓的参与者是指所有存在于系统外部并与系统进行交互的人或其他系统。通俗地讲,参与者就是我们所要定义系统的使用者。寻找参与者可以从以下问题入手:

  • 系统开发完成之后,有哪些人会使用这个系统?
  • 系统需要从哪些人或其他系统中获得数据?
  • 系统会为哪些人或其他系统提供数据?
  • 系统会与哪些其他系统相关联?
  • 系统是由谁来维护和管理的?

这些问题有助于我们抽象出系统的参与者。对于ATM机的例子,回答这些问题可以使我们找到更多的参与者:操作员负责维护和管理ATM机系统、ATM机也需要与后台服务器进行通讯以获得有关用户帐号的相关信息。

系统边界决定了参与者

参与者是由系统的边界所决定的,如果我们所要定义的系统边界仅限于ATM机本身,那么后台服务器就是一个外部的系统,可以抽象为一个参与者。

如果我们所要定义的系统边界扩大至整个银行系统,ATM机和后台服务器都是整个银行系统的一部分,这时候后台服务器就不再被抽象成为一个参与者。

值得注意的是,用例建模时不要将一些系统的组成结构作为参与者来进行抽象,如在ATM机系统中,打印机只是系统的一个组成部分,不应将它抽象成一个独立的参与者;在一个MIS管理系统中,数据库系统往往只作为系统的一个组成部分,一般不将其单独抽象成一个参与者。

特殊的参与者――系统时钟

有时候我们需要在系统内部定时地执行一些操作,如检测系统资源使用情况、定期地生成统计报表等等。从表面上看,这些操作并不是由外部的人或系统触发的,应该怎样用用例方法来表述这一类功能需求呢?对于这种情况,我们可以抽象出一个系统时钟或定时器参与者,利用该参与者来触发这一类定时操作。从逻辑上,这一参与者应该被理解成是系统外部的,由它来触发系统所提供的用例对话。

(2)确定用例

找到参与者之后,我们就可以根据参与者来确定系统的用例,主要是看各参与者需要系统提供什么样的服务,或者说参与者是如何使用系统的。寻找用例可以从以下问题入手(针对每一个参与者):

  • 参与者为什么要使用该系统?
  • 参与者是否会在系统中创建、修改、删除、访问、存储数据?如果是的话,参与者又是如何来完成这些操作的?
  • 参与者是否会将外部的某些事件通知给该系统?
  • 系统是否会将内部的某些事件通知该参与者?

综合以上所述,ATM系统的用例图可表示如下,

在用例的抽取过程中,必须注意:用例必须是由某一个主角触发而产生的活动,即每个用例至少应该涉及一个主角。如果存在与主角不进行交互的用例,就可以考虑将其并入其他用例;或者是检查该用例相对应的参与者是否被遗漏,如果是,则补上该参与者。反之,每个参与者也必须至少涉及到一个用例,如果发现有不与任何用例相关联的参与者存在,就应该考虑该参与者是如何与系统发生对话的,或者由参与者确定一个新的用例,或者该参与者是一个多余的模型元素,应该将其删除。

可视化建模的主要目的之一就是要增强团队的沟通,用例模型必须是易于理解的。用例建模往往是一个团队开发的过程,系统分析员在建模过程中必须注意参与者和用例的名称应该符合一定的命名约定,这样整个用例模型才能够符合一定的风格。如参与者的名称一般都是名词,用例名称一般都是动宾词组等。

对于同一个系统,不同的人对于参与者和用例都可能有不同的抽象结果,因而得到不同的用例模型。我们需要在多个用例模型方案中选择一种"最佳"(或"较佳")的结果,一个好的用例模型应该能够容易被不同的涉众所理解,并且不同的涉众对于同一用例模型的理解应该是一致的。

(3)描述用例规约

应该避免这样一种误解――认为由参与者和用例构成的用例图就是用例模型,用例图只是在总体上大致描述了系统所能提供的各种服务,让我们对于系统的功能有一个总体的认识。除此之外,我们还需要描述每一个有例的详细信息,这些信息包含在用例规约中,用例模型是由用例图和每一个用例的详细描述――用例规约所组成的。RUP中提供了用例规约的模板,每一个用例的用例规约都应该包含以下内容:

  • 简要说明 (Brief Description)

简要介绍该用例的作用和目的。

  • 事件流 (Flow of Event)

包括基本流和备选流,事件流应该表示出所有的场景。

  • 用例场景 (Use-Case Scenario)
  • 包括成功场景和失败场景,场景主要是由基本流和备选流组合而成的。
  • 特殊需求 (Special Requirement)
  • 描述与该用例相关的非功能性需求(包括性能、可靠性、可用性和可扩展性等)和设计约束(所使用的操作系统、开发工具等)。
  • 前置条件 (Pre-Condition)
  • 执行用例之前系统必须所处的状态。
  • 后置条件 (Post-Condition)
  • 用例执行完毕后系统可能处于的一组状态。

用例规约基本上是用文本方式来表述的,为了更加清晰地描述事件流,也可以选择使用状态图、活动图或序列图来辅助说明。只要有助于表达的简洁明了,就可以在用例中任意粘贴用户界面和流程的图形化显示方式,或是其他图形。如活动图有助于描述复杂的决策流程,状态转移图有助于描述与状态相关的系统行为,序列图适合于描述基于时间顺序的消息传递。

基本流

基本流描述的是该用例最正常的一种场景,在基本流中系统执行一系列活动步骤来响应参与者提出的服务请求。建议用以下格式来描述基本流:

1) 每一个步骤都需要用数字编号以清楚地标明步骤的先后顺序。

2) 用一句简短的标题来概括每一步骤的主要内容,这样阅读者可以通过浏览标题来快速地了解用例的主要步骤。在用例建模的早期,我们也只需要描述到事件流步骤标题这一层,以免过早地陷入到用例描述的细节中去。

3) 当整个用例模型基本稳定之后,我们再针对每一步骤详细描述参与者和系统之间所发生的交互。建议采用双向(roundtrip)描述法来保证描述的完整性,即每一步骤都需要从正反两个方面来描述:(1)参与者向系统提交了什么信息;(2)对此系统有什么样的响应。

在描述参与者和系统之间的信息交换时,需指出来回传递的具体信息。例如,只表述参与者输入了客户信息就不够明确,最好明确地说参与者输入了客户姓名和地址。通常可以利用词汇表让用例的复杂性保持在可控范围内,可以在词汇表中定义客户信息等内容,使用例不至于陷入过多的细节。

2.3.2 备选流

备选流负责描述用例执行过程中异常的或偶尔发生的一些情况,备选流和基本流的组合应该能够覆盖该用例所有可能发生的场景。在描述备选流时,应该包括以下几个要素:

1) 起点:该备选流从事件流的哪一步开始;

2) 条件:在什么条件下会触发该备选流;

3) 动作:系统在该备选流下会采取哪些动作;

4) 恢复:该备选流结束之后,该用例应如何继续执行。

备选流的描述格式可以与基本流的格式一致,也需要编号并以标题概述其内容,编号前可以加以字母前缀A(Alternative)以示与基本流步骤相区别。

用例场景

用例在实际执行的时候会有很多的不同情况发生,称之为用例场景;也可以说场景是用例的实例,我们在描述用例的时候要覆盖所有的用例场景,否则就有可能导致需求的遗漏。在用例规约中,场景的描述可以由基本流和备选流的组合来表示。场景既可以帮助我们防止需求的遗漏,同时也可以对后续的开发工作起到很大的帮助:开发人员必须实现所有的场景、测试人员可以根据用例场景来设计测试用例。

特殊需求

特殊需求通常是非功能性需求,它为一个用例所专有,但不适合在用例的事件流文本中进行说明。特殊需求的例子包括法律或法规方面的需求、应用程序标准和所构建系统的质量属性(包括可用性、可靠性、性能或支持性需求等)。此外,其他一些设计约束,如操作系统及环境、兼容性需求等,也可以在此节中记录。

需要注意的是,这里记录的是专属于该用例的特殊需求;对于一些全局的非功能性需求和设计约束,它们并不是该用例所专有的,应把它们记录在《补充规约》中。

前置和后置条件

前置条件是执行用例之前必须存在的系统状态,后置条件是用例一执行完毕后系统可能处于的一组状态。

检查用例模型

用例模型完成之后,可以对用例模型进行检查,看看是否有遗漏或错误之处。主要可以从以下几个方面来进行检查:

  • 功能需求的完备性

现有的用例模型是否完整地描述了系统功能,这也是我们判断用例建模工作是否结束的标志。如果发现还有系统功能没有被记录在现有的用例模型中,那么我们就需要抽象一些新的用例来记录这些需求,或是将他们归纳在一些现有的用例之中。

  • 模型是否易于理解

用例模型最大的优点就在于它应该易于被不同的涉众所理解,因而用例建模最主要的指导原则就是它的可理解性。用例的粒度、个数以及模型元素之间的关系复杂程度都应该由该指导原则决定。

  • 是否存在不一致性

系统的用例模型是由多个系统分析员协同完成的,模型本身也是由多个工件所组成的,所以我们要特别注意不同工件之前是否存在前后矛盾或冲突的地方,避免在模型内部产生不一致性。不一致性会直接影响到需求定义的准确性。

  • 避免二义性语义

好的需求定义应该是无二义性的,即不同的人对于同一需求的理解应该是一致的。在用例规约的描述中,应该避免定义含义模糊的需求,即无二义性。

3. 系统需求

RUP中根据FURPS+模型将系统需求分为以下几类:

除了第一项功能性需求之外的其他需求都归之为非功能性需求。

(1)需求工件集

用例模型主要用于描述系统的功能性需求,对于其他的非功能性需要用其他文档来记录。RUP中定义了如下的需求工件集合。

  • 用例模型:记录功能性需求
    • 用例图:描述参与者和用例之间的关系
    • 用例规约:描述每一个用例的细节信息
  • 补充规约:记录一些全局性的功能需求、非功能性需求和设计约束等
  • 词汇表:记录一些系统需求相关的术语

在实际应用中,除了这些工件之外,我们还可以根据实际需求灵活选用其他形式的文档来补充说明需求。并不是所有的系统需求都适保合用用例模型来描述的,如编译器,我们很难用用例方法来表述它所处理的语言的方法规则,在这种情况下,采用传统的BNF范式来表述更加合适一些。在电信软件行业中,很多电信标准都是采用SDL语言来描述的,我们也不必用UML来改写这些标准,只需将SDL形式的电信标准作为需求工件之一,在其他工件中对其加以引用就可以了。总之,万万不可拘泥于用例建模的形式,应灵活运用各种方式的长处。

(2) 补充规约

补充规约记录那些在用例模型中不易表述的系统需求,主要包括以下内容。

  • 功能性
  • 功能性需求主要在用例模型中刻画,但是也有部分需求不适合在用例中表述。有些功能性需求是全局性的,适用于所有的用例,如出错处理、I18N支持等,我们不需要在所有的用例中描述这些功能性需求,只需要在补充规约中统一描述就可以了。
  • 可用性
  • 记录所有可用性相关的需求,如系统的使用者所需要的培训时间、是否应附合一些常见的可用性标准如Windows界面风格等。
  • 可靠性
  • 定义系统可靠性相关的各种指标,包括:
    • 可用性:指出可用时间百分比(xx.xx%),系统处于使用、维护、降级模式等操作的小时数;
    • 平均故障间隔时间(MTBF):通常表示为小时数,但也可表示为天数、月数或年数;
    • 平均修复时间(MTTR):系统在发生故障后可以暂停运行的时间;
    • 精确度:指出系统输出要求具备的精密度(分辨率)和精确度(按照某一已知的标准);
    • 最高错误或缺陷率:通常表示为bugs/KLOC(每千行代码的错误数目)或bugs/function-point(每个功能点的错误数目)。
  • 性能
  • 记录系统性能相关的各种指标,包括:
    • 对事务的响应时间(平均、最长);
    • 吞吐量(例如每秒处理的事务数);
    • 容量(例如系统可以容纳的客户或事务数);
    • 降级模式(当系统以某种形式降级时可接受的运行模式);
    • 资源利用情况:内存、磁盘、通信等。
  • 可支持性
  • 定义所有与系统的可支持性或可维护性相关的需求,其中包括编码标准、命名约定、类库、如何来对系统进行维护操作和相应的维护实用工具等。
  • 设计约束
  • 设计约束代表已经批准并必须遵循的设计决定,其中包括软件开发流程、开发工具、系统构架、编程语言、第三方构件类库、运行平台和数据库系统等等。

(3) 词汇表

词汇表主要用于定义项目特定的术语,它有助于开发人员对项目中所用的术语有统一的理解和使用,它也是后续阶段中进行对象抽象的基础。

4. 调整用例模型

在一般的用例图中,我们只表述参与者和用例之间的关系,即它们之间的通讯关联。除此之外,我们还可以描述参与者与参与者之间的泛化(generalization)、用例和用例之间的包含(include)、扩展(extend)和泛化(generalization)关系。我们利用这些关系来调整已有的用例模型,把一些公共的信息抽取出来重用,使得用例模型更易于维护。但是在应用中要小心选用这些关系,一般来说这些关系都会增加用例和关系的个数,从而增加用例模型的复杂度。而且一般都是在用例模型完成之后才对用例模型进行调整,所以在用例建模的初期不必要急于抽象用例之间的关系。

(1) 参与者之间的关系

参与者之间可以有泛化(Generalization)关系(或称为"继承"关系)。例如在需求分析中常见的权限控制问题(如下图所示),一般的用户只可以使用一些常规的操作,而管理员除了常规操作之外还需要进行一些系统管理工作,操作员既可以进行常规操作又可以进行一些配置操作。

在这个例子中我们会发现管理员和操作员都是一种特殊的用户,他们拥有普通用户所拥有的全部权限,此外他们还有自己独有的权限。这里我们可进一步把普通用户和管理员、操作员之间的关系抽象成泛化(Generalization)关系,管理员和操作员可以继承普通用户的全部特性(包括权限),他们又可以有自己独有的特性(如操作、权限等)。这样可以显著减速少用例图中通讯关联的个数,简化用例模型,使之更易于理解。

(2) 用例之间的关系

用例描述的是系统外部可见的行为,是系统为某一个或几个参与者提供的一段完整的服务。从原则上来讲,用例之间都是并列的,它们之间并不存在着包含从属关系。但是从保证用例模型的可维护性和一致性角度来看,我们可以在用例之间抽象出包含(include)、扩展(extend)和泛化(generalization)这几种关系。这几种关系都是从现有的用例中抽取出公共的那部分信息,然后通后过不同的方法来重用这部公共信息,以减少模型维护的工作量。

包含(include)

包含关系是通过在关联关系上应用<<include>>构造型来表示的,如下图所示。它所表示的语义是指基础用例(Base)会用到被包含用例(Inclusion),具体地讲,就是将被包含用例的事件流插入到基础用例的事件流中。

包含关系是UML1.3中的表述,在UML1.1中,同等语义的关系被表述为使用(uses),如下图。

在ATM机中,如果查询、取现、转帐这三个用例都需要打印一个回执给客户,我们就可以把打印回执这一部分内容提取出来,抽象成为一个单独的用例"打印回执",而原有的查询、取现、转帐三个例都会包含这个用例。每当以后要对打印回执部分的需求进行修改时,就只需要改动一个用例,而不用在每一个用例都作相应修改,这样就提高了用例模型的可维护性。

在基础用例的事件流中,我们只需要引用被包含用例即可。

查询-基本事件流

1. 用户插入信用卡

2. 输入密码

3. 选择查询

4. 查看帐号余额

5. 包含用例"打印回执"

6. 退出系统,取回信用卡

在这个例子中,多个用例需要用到同一段行为,我们可以把这段共同的行为单独抽象成为一个用例,然后让其他的用例来包含这一用例。从而避免在多个用例中重复性地描述同一段行为,也可以防止该段行为在多个用例中的描述出现不一致性。当需要修改这段公共的需求时,我们也只需要修改一个用例,避免同时修改多个用例而产生的不一致性和重复性工作。

有时当某一个用例的事件流过于复杂时,为了简化用例的描述,我们也可以把某一段事件流抽象成为一个被包含的用例。这种情况类似于在过程设计语言中,将程序的某一段算法封装成一个子过程,然后再从主程序中调用这一子过程。

扩展(extend)

扩展(extend)关系如下图所示,基础用例(Base)中定义有一至多个已命名的扩展点,扩展关系是指将扩展用例(Extension)的事件流在一定的条件下按照相应的扩展点插入到基础用例(Base)中。对于包含关系而言,子用例中的事件流是一定插入到基础用例中去的,并且插入点只有一个。而扩展关系可以根据一定的条件来决定是否将扩展用例的事件流插入基础用例事件流,并且插入点可以有多个。

例如对于电话业务,可以在基本通话(Call)业务上扩展出一些增值业务如:呼叫等待(Call Waiting)和呼叫转移(Call Transfer)。我们可以用扩展关系将这些业务的用例模型描述如下。

在这个例子中,呼叫等待和呼叫转移都是对基本通话用例的扩展,但是这两个用例只有在一定的条件下(如应答方正忙或应答方无应答)才会将被扩展用例的事件流嵌入基本通话用例的扩展点,并重用基本通话用例中的事件流。

值得注意的是扩展用例的事件流往往可以也可抽象为基础用例的备选流,如上例中的呼叫等待和呼叫转移都可以作为基本通话用例的备选流而存在。但是基本通话用例已经是一个很复杂的用例了,选用扩展关系将增值业务抽象成为单独的用例可以避免基础用例过于复杂,并且把一些可选的操作独立封装在另外的用例中。

泛化(generalization)

当多个用例共同拥有一种类似的结构和行为的时候,我们可以将它们的共性抽象成为父用例,其他的用例作为泛化关系中的子用例。在用例的泛化关系中,子用例是父用例的一种特殊形式,子用例继承了父用例所有的结构、行为和关系。在实际应用中很少使用泛化关系,子用例中的特殊行为都可以作为父用例中的备选流存在。

以下是一个用例泛化关系的例子,执行交易是一种交易抽象,执行房产交易和执行证券交易都是一种特殊的交易形式。

用例泛化关系中的事件流示例如下:

(2) 调整用例模型

用例模型建成之后,我们可以对用例模型进行检视,看是否可以进一步简化用例模型、提高重用程度、增加模型的可维护性。主要可以从以下检查点(checkpoints)入手:

  • 用例之间是否相互独立?如果两个用例总是以同样的顺序被激活,可能需要将它们合并为一个用例。
  • 多个用例之间是否有非常相似的行为或事件流?如果有,可以考虑将它们合并为一个用例。
  • 用例事件流的一部分是否已被构建为另一个用例?如果是,可以让该用例包含(include)另一用例。
  • 是否应该将一个用例的事件流插入另一个用例的事件流中?如果是,利用与另一个用例的扩展关系(extend)来建立此模型。

5. 管理用例模型复杂度

一般小型的系统,其用例模型中包含的参与者和用例不会太多,一个用例图就可以容纳所有的参与者,所有的参与者和用例也可以并存于同一个层次结构中。对于较复杂的大中型系统,用例模型中的参与者和用例会大大增加,我们需要一些方法来有效地管理由于规模上升而造成的复杂度。

(1) 用例包

包(Package)是UML中最常用的管理模型复杂度的机制,包也是UML中语义最简单的一种模型元素,它就是一种容器,在包中可以容纳其他任意的模型元素(包括其他的包)。在用例模型中,我们可以用构造型(Sterotype)<<use case>>来扩展标准UML包的语义,这种新的包叫作用例包(Use Case Package),用于分类管理用例模型中的模型元素。

我们可以根据参与者和用例的特性来对它们进行分类,分别置于不同的用例包管理之下。例如对于一个大型的企业管理信息系统,我们可以根据参与者和用例的内容将它们分别归于人力资源、财务、采购、销售、客务服务这些用例包之下。这样我们将整个用例模型划分成为两个层次,在第一层次我们看到的是系统功能总共分为五部分,在第二层次我们可以分别看到每一用例包内部的参与者和用例。

一个用例模型需要有多少个用例包取决你想怎么样来管理用例模型的复杂度(包括参与者和用例的个数,以及它们之间的相互关系)。UML中的包其实就类似于文件系统中的目录,文件数量少的时候不需要额外的目录,文件数量一多就需要有多个目录来分类管理,同样一组文件不同的人会创建不同的目录结构来进行管理,关键是要保证在目录结构下每一个文件都要易于访问。同样的道理存在于用例建模之中,如何创建用例包以及用例包的个数取决于不同的系统和系统分析员,但要保证整个用例模型易于理解。

(2) 用例的粒度

系统需要有多少个用例?这是很多人在用例建模时会产生的疑惑。描述同一个系统,不同的人会产生不同的用例模型。例如对于各种系统中常见的"维护用户"用例,它里面包含了添加用户、修改用户信息、删除用户等操作,这些操作在该用例的事件流可以表述成为基本流的子事件流(subflow)。

维护用户-基本事件流

该基本流由三个子事件流构成:

1) 添加用户子事件流

2) 修改用户子事件流

3) 删除用户子事件流

但是你也可以根据该用例中的具体操作把它抽象成为三个用例,它所表示的系统需求和单个用例的模型是完全一样的。

应该如何确定用例的粒度呢?在一次技术研讨会上,有人问起Ivar Jacoboson博士,一个系统需要有多少个用例?大师的回答是20个,当然他的意思是最好将用例模型的规模控制在几十个用例左右,这样比较容易来管理用例模型的复杂度。在用例个数大致确定的条件下,我们就很容易来确定用例粒度的大小。对于较复杂的系统,我们需要控制用例模型一级的复杂度,所以可以将复杂度适当地移往每一个用例的内部,也就是让一个用例包含较多的需求信息量。     对于比较简单的系统,我们则可以将复杂度适度地曝露在模型一级,也就是我们可以将较复杂的用例分解成为多个用例。

用例的粒度不但决定了用例模型级的复杂度,而且也决定了每一个用例内部的复杂度。我们应该根据每个系统的具体情况,因时因宜地来把握各个层次的复杂度,在尽可能保证整个用例模型的易理解性前提下决定用例的大小和数目。

(2) 用例图

用例图的主要作用是描述参与者和用例之间的关系,简单的系统中只需要有一个用例图就可以把所有的关系都描述清楚。复杂的系统中可以有多个用例图,例如每个用例包都可以有一个独立的用例图来描述该用例包中所有的参与者和用例的关系。

在一个用例模型中,如果参与者和用例之间存在着多对多的关系,并且他们之间的关系比较复杂,如果在同一个用例图中表述所有的参与者和用例就显得不够清晰,这时我们可创建多个用例图来分别表示各种关系。

如果想要强调某一个参与者和多个用例的关系,你就可以以该参与者为中心,用一个用例图表述出该参与者和多个用例之间的关系。在这个用例图中,我们强调的是该参与者会使用系统所提供的哪些服务。

如果想要强调某一个用例和多个参与者之间的关系,你就可以以该用例为中心,用一个用例图表述出该用例和多个参与者之间的关系。在这个用例图中,我们强调的是该用例会涉及到哪些参与者,或者说该用例所表示的系统服务有哪些使用者。

总之在用例建模过程中,你可以根据自己的需要创建任意多个用例图,用不同的用例来强调参与者和用例之间不同的关系。但是最重要的是要考虑整个用例模型的可理解性,如果可以用一个用例图把意思表述清楚,就不要再用第二个,因为越是简洁的模型越易于理解。


七 如何有效使用UML

画 UML 图是一种非常有用的活动,它也可能成为一种浪费时间的、可怕的活动。使用 UML 的决定是一件好事,也可能成为一件坏事。它依赖你确定怎么使用、多大范围内的使 用它。

1 人员之间传达

UML在软件开发人员之间传达设计概念是非常方便的。在一小群开发人员中,如果你有一些主意需要传达给其他人员,用UML可能是非常好的。

UML用于表达集中的设计思想相当不错,另一方面,在表达算法细节时UML不是特别理想。

仅用一定数量的、需要的细节去完成你的目标。一个充斥着修饰符号的图不是不可以,但是它是效能低下的。保持你的图简洁,UML图不是源代码,也不能视作方法、变量和关系声明。

UML在创建大型软件结构的“路标图”(roadmaps)时是比较有用,这样的“路标图”给开发人员一个快速的手段,用来发现某一个类依赖于另外哪些类,并为整个系统的结构的提供了一个参考。在代码中去发现这些结构是相当乏味的、累人的,而在“路标图”中去发现结构却是轻而易举的。如下图所示。

相反的,没有比一个由许多线和许多框纠缠在一起组成的混乱的UML图更糟糕的情况了,如下图所示。

2 最后的文档

任何一个项目都离不开好的文档,没有了文档,团队将迷失在代码的海洋里。从另外一方面来说,太多错误的文档也是糟糕的,那些分散精力和令人误解的内容仍将使团队迷失在代码的海洋里。

必须建立文档,同时必须谨慎地创建文档,通常选择不创建文档和创建文档是一样重要的。例如,一个复杂的通讯协议需要创建文档,一个复杂的关系型模式需要创建文档,一个复杂的可重用的框架需要创建文档。

无须上百页的UML文档。软件文档应该言简意赅,一个软件文档的价值通常与文档的大小成反比。需要花一些功夫去让文档变小,这样的工作是有意义的。人们将会阅读小的文档,而不是上千页的大部头。

对于一个有12个人共同工作、编写100万行Java代码的项目团队,预计固定保存的文档将会是25到200页。这包括一些有关重要模块的高层结构的UML 图的文档、关系型模式的 ER 图、一到两页纸描述如何建立系统、测试指令、源代码控制指令等等。

什么时候最适合创建一个设计文档呢?最好在团队所有的工作干完了,项目要结束了的时候。这样的文档真实地反映了一个团队工作结果的状况,它对接着即将开展工作的团队极有帮助。

3 保留和舍弃

大多数 UML 图都是短命的,养成舍弃UML图的习惯吧,最好养成不要把图建立在能长期保存的介质上习惯。在一个白板或草稿纸上画它们,并且经常擦掉白板或丢弃草稿纸。

只有以下这些图保存下来非常有用:

ü  表现你的系统中一个通用设计解决方案的图

ü  记录了复杂的协议,难以通过代码了解的图

ü  提供了比较少涉及到的系统范围内的“路标图”的图

ü  记录了比代码更易表述的设计意图的图

4 什么时候画图

不要制定什么都必须画图的规则,这样的规则将比不用更糟糕。项目的大量时间被浪费在那些根本没有人去读的图上。

什么时候画图

ü  当许多人一起需要同时进行开发时,这些人需要都理解一个系统的特定部分的设计结构时,开始画图。当所有的人都已经声明理解了的时候,结束画图。

ü  当两个人或更多人不同意一个特定的元素如何设计的时候,你需要你的团队意见一致的时候,要找一个时间进行讨论做出决定,比如投票,或一个公正的宣告的方式进行,这时你需要画图。当决定做出来后,擦掉这些图。

ü  当你需要探讨一个设计的想法时,画图能够帮你更好的地思考。当你得到了能够帮助你完成思考的代码的要点的时候,扔掉这些图。

ü  当你需要向其他人或自己解释一部分代码的结构的时候,你可以画图。当你觉得其实最好看代码来进行解释的时候,停止画图。

ü  当项目快要结束,顾客需要你将图与其他文档一起提供的时候,开始画图

什么时候不画图:

ü  不要因为觉得过程需要而画图

ü  不要觉得一个好的设计者必须画图,不画图反而感觉有罪的想法而去画图。好的设计者只有在需要的时候才去编写代码和画图。

ü  不要在编码之前的设计阶段去为创建全面的文档而画图,这样的文档基本没有意义并且将会浪费大量的时间。

总结

UML是一种工具,工具本身不是最终的目的。作为一种工具,它能够帮助你思考设计和在同事间进行信息传达。节制地使用它们,它们将给你极大的帮助。如果滥用它的话,它将极大地浪费你的时间。当用到UML的时候,尽量精简!

统一建模语言(UML)相关推荐

  1. 统一建模语言(UML) 版本 2.0

    原文: http://www.ibm.com/developerworks/cn/rational/321_uml/ 简介 参考 UML 基础系列的其他文章和教程 UML基础: 统一建模语言简介 UM ...

  2. 软考初级程序员易错题:在统一建模语言 (UML)中,( )展现了 一组对象以及它们之间的关系, 给出了系统的静态设计视图或静态进程视图,描述了( )中所建立的事物 实例的静态快照。

    在统一建模语言 (UML)中,( )展现了 一组对象以及它们之间的关系, 给出了系统的静态设计视图或静态进程视图,描述了( )中所建立的事物 实例的静态快照. 知识点: 统一建模语言 (UML) 问题 ...

  3. 统一建模语言——UML(第9-20课时)(cont.)

    文章目录 UML概述 UML是什么 UML能做什么 为什么要学UML UML构造需求模型 用例建模技术 绘制用例图 识别执行者 识别用例 检查形式 关联关系 编写用例文档 用例文档的组成部分 详细说明 ...

  4. 6 统一建模语言(UML)图中,( )用于对时间如何改变对象的状态以及引起对象从一个状态向另一个状态转换的事件进行建模。( )是此种图的一种特殊情况,对于系统的功能建模特别重要,并强调对象间的控制

    标签: 软考初级程序员易错题,计算机基础, 面向对象基础,统一建模语言 (UML) 6 统一建模语言(UML)图中,( )用于对时间如何改变对象的状态以及引起对象从一个状态向另一个状态转换的事件进行建 ...

  5. 【中级软考】UML图(Unified Modeling Language统一建模语言、标准建模语言)

    文章目录 UML图 UML图的作用 主要模型 功能模型 对象模型 动态模型 图的种类 用例图 类图 对象图 包图 活动图 状态图 1.概述: 2.构成要素: 2.1状态(States): 2.2转移( ...

  6. UML统一建模语言知识体系概述

    UML统一建模语言知识体系概述 http://developer.51cto.com/  2006-01-11 16:46    noahsports.com  我要评论(3 ) UML是一种定义良好 ...

  7. 统一建模语言(UML)介绍

    统一建模语言 科技名词定义 中文名称: 统一建模语言 英文名称: unified modeling language;UML 定义: 是一种面向对象的建模语言,它是运用统一的.标准化的标记和定义实现对 ...

  8. UML(Unified Modeling Language)统一建模语言--类图详解

    UML(Unified Modeling Language)统一建模语言--类图详解   2010-05-12 作者:fenglin1985 来源:fenglin1985的blog   类图是面向对象 ...

  9. UML基础: 统一建模语言简介

    回顾20世纪晚期--准确地说是1997年,OMG组织(Object Management Group对象管理组织)发布了统一建模语言(Unified Modeling Language,UML).UM ...

  10. [本体论][UML][统一建模语言][软件建模][OWL]从本体论到UML到OWL

    以下内容,是关于软件建模的方法与思路. UML与OWL都是基于本体论的建模语言. 本体论(哲学) 本体论(信息科学) UML(统一建模语言) more info 参考:[设计语言][统一建模语言][软 ...

最新文章

  1. 这五种 Python 字符串连接方式,你都知道吗?
  2. 多重循环控制(难点重点)
  3. TCP/IP协议与Http协议的区别详解
  4. PineApple_Ninja.js
  5. java 读取 邮件 附件,JavaMail 中对附件下载的处理
  6. dnn神经网络 缺点_抄近路神经网络如何因找捷径而犯错
  7. 代码审计系列篇一之代码审计学习思路
  8. linux之ClamAV杀毒软件安装配置
  9. 世界以痛吻我,我要报之以歌
  10. 带你区分超键,候选键,主键
  11. 头条App项目测试实战(三)文章写评论功能用例设计
  12. 2019-2-14SQLserver中拼音查询数据
  13. “蔚来杯“2022牛客暑期多校训练营1 J Serval and Essay(启发式合并)
  14. Spring Cloud 入门 ---- Security 整合 Oauth2 认证授权【随笔】
  15. Mac创建自签名的代码证书
  16. zz from 古文中惊艳的句子
  17. [Splay的应用]
  18. c语言魔方还原程序案例,利用C语言玩转魔方阵实例教程
  19. 关于lightroom 安装出现乱码问题
  20. java基础学习(五)

热门文章

  1. uni map地图相关使用小计(多点标识,拖拽起点,地图画圆)
  2. 树莓派3B+ 串口使用大全(实现串口通信功能)
  3. 哈尔滨工业大学-自适应信号处理-QRD-RLS算法实验
  4. 如何用Delphi做外挂初级版
  5. ICAF开幕 嵌入式成未来互联网主旋律
  6. Web前端开发——CSS样式(Ⅱ)背景、超链接样式
  7. 华为DHCP配置基础试验
  8. linux驱动开发 - 10_阻塞和非阻塞 IO
  9. 木马程序的配置和使用实验
  10. [ 数据通信面试篇 ] 数通面试题大集合(详解),看完直怼面试官(五)