用例分析
从用例到代码, 第一部分: 用例分析 |
级别: 初级 Gary Evans, Independent Object Technology Evangelist, Evanetics 2005 年 4 月 01 日
自
IBM Rational Unified Process(RUP)
在RUP 在这里,用一个虚构的软件项目”green-field” 作为例子。这是一个面向对象的软件项目,使用UML来描述系统中的各种概念与关系。读者需要具有对象和类的基础知识,熟悉UML 版本1.x 或者2.0中的类图、顺序图和协作图。 下面我们讨论一下RUP中的用例分析。如图1所示,将RUP中结构分析的结果整合起来。
图1: 结构分析的工作流程(early Elaboration) 显而易见,严格来说的话,软件开发过程应该侧重于企业系统级的架构设计,以及软件重用。但是基于以下三点原因,在这里我不会长篇大论的讲述结构分析:
用例分析的目的:
我们也可以认为,用例分析的目标,就是把我们对用例的理解,转变为与业务一致的形式,实现需求的价值。在用例设计的时候,我们把业务概念抽象成类、对象、关系、组件、接口等等,这些都与目标系统直接对应。 图2引自RUP分析和设计概述部分,描述了需要使用用例分析的场景,和用例分析与其它步骤之间的关系。
在RUP [RUP2003]中的用例分析由几部分组成:
请 如图3所示,一些步骤把编写用例和实现代码分隔开了。还列出了RUP中用例分析部分推荐使用的一些步骤。这张图将会指出在后面部分中,我们的前进方向。
例子可以更好的说明问题。这个虚构的简短例子是一个汽车租赁公司的网站系统。这种系统一般有几个用例描述了为客户提供的服务,如:
为了简化模型,假设我们的服务只针对个人用户,不针对企业用户。 为了让例子尽量简单、易于理解,我们只分析其中的一个用例。描述如下:预约汽车.
必 在设计web界面时,可以把多个步骤放在同一个页面中,例如上面描述中的步骤2和3。在web环境中,第7步的确认,将会在预约交易报告页面中呈现给顾客。 也 我们准备好的用例描述,可以作为下一步的起点。下面让我们开始按照RUP中的用例分析过程来开始分析。
RUP 由于我们是在设计一个面向对象的软件系统,我们的用例行为需要由我们系统中的一些对象和类来执行。但是迄今为止,我们还没有任何对象和类,因此我们需要从用例中去发现这些对象和类。还需要指定每个类要执行用例图中的哪些行为。 如图4所示,用例实现实际上是一组UML图,说明了我们都需要哪些类、它们的职责和它们的实例对象之间如何进行交互,来完成用例中的行为。
通常一个用例实现会由下面这些组成:
看起来我们有很多事情需要处理,是这样吗?是的,而且当你使用诸如Rational Rose或Rational
当你在进行分析的时候,你的用例描述只记录了从系统外面的用户角度来看,系统的行为是什么样子的。在概要的描述系统内部的一些不可见的操作的时候,这足够了,但是按照这样的描述,并不能完成具体的实现。 举 这里我们给出了一个汽车数据库的信息,并且比较抽象的描述了如何用网页来访问它。这是一个很简单的例子,但是读者可以从中了解一些内容。 在RUP这样的迭代开发流程中,从分析阶段转移到设计阶段只用了很少的时间。对一个中等规模的,四周左右的项目来说,你可能第一周用来获取需求, 做你的分析和设计,然后后面3周都用来写代码和进行测试。你用于分析的用例会侧重于系统对外可见的行为, 假设我们的系统是一个网站程序。当客户需要的时候,我们会为他们提供私人在线预约汽车服务。我们要为这个实现的具体情况来细化我们的用例,不过不要过多的涉及到设计阶段的工作。 这是预约汽车用例的更详细的描述,还是侧重于做什么,而不是侧重于怎么做:
在这个经过补充的用例描述中,我们清晰的描述了一个基于浏览器的程序的行为,描述了很多对顾客来说不可见的行为。但是并没有提供设计级别的信息。 不需要。但是请记住,补充细节,并不是指实现的具体细节。我们的目标是为了能够有足够的信息来理解系统中的分析类,并与你的顾客或分析人员就所有用例的业务流程达成一致。 如果你觉得补充一些细节对于找出系统中的分析类有所帮助,那么就加上它。 注意:把握这个细节的尺度会比较困难。需要时间和一些练习。多上手练习,向别人多询问,还要记住当你无法决定的时候,应该稍微偏向抽象一点的描述。增加一些没有写上去的内容比较容易做到,但是要从很多杂乱的描述中找到有用的信息可就难的多了。 为什么还要先写出比较抽象的用例呢?为什么不直接把用例补充的比较详细? 这
根据RUP过程,这一步的目的是找出分析类的候选范围,这些类合作起来,可以完成用例的所有行为。因为我们还没有任何类,所以我们的主要目标就是找出在汽车租借系统中的所有分析类。 这就引出了一个有趣而又重要的问题:什么是分析类?有两种答案。首先,一个业务级别的分析类是业务领域中的一个要素,与实现技术无关。例如,银行系统中的银行顾客、帐户、帐号交易等等。而且它与系统的实现无关,不管是一个新的电子商务系统,或是一个从19世纪80年代就开始使用的借贷系统。 其次,RUP过程把这个定义扩展出三种不同的分析类:实体类、控制类和边界类。RUP过程中的实体类大致上相当于前面提到的,业务级别的分析类。控制类与业务过程相关,它们控制整个业务的流程和执行次序。它通常控制一个用例中业务过程的执行。边界类在系统与外界之间,为它们交换各种信息与事件。边界类处理软件系统的输入与输出。 根 让我们回顾一下,用例描述的是行为,也就是系统为用户提供了什么样的服务。在用例描述中,没有涉及面向对象的内容,但是这些描述是用来找出系统中的对象和类的。可以在很多地方,用各种各样的方法来寻找类:
寻找类的一个简单的方法就是语法分析,下面我将加以说明。我们只要找出我们的需求中的名词, 这些名词(有些是形容词+名词):
找出预约汽车的用例描述中的这些名词(跳过代名词,如“他”),如下: 用例:为顾客预约汽车(补充)
注意每个名词,或者形容词+名词的组合, 都被标出来了。有很多重复的,因此把每个词单独列入词汇表1,按字母顺序排序:
我们如何分辨出哪些候选的名词才是真正的问题领域中的类呢?一个常用的方法就是,用一些简单的问题来测试每个词,如图5:
如果某一个答案是“不是”,那么这个候选词很可能不是一个类。再继续检查下一个词。如果答案是“是的”,那么继续检查下一个问题,如果所有的问题的答案都是肯定的,这个候选词就是一个类。再继续检查下一个词。
我们对每个候选词做检查,就会得到类似于表2的结果:
请注意租借地点已经被加进去了,虽然它不是用例的一部分。在与我们的业务专家(Subject Matter Experts,SMEs)们谈话时我们发现,业务中会使用
从这个列表中,我们列出了通过了问题检查的词,也就是下面列出的分析类:
啊哈!在总共得30多个候选中,现在我们只需要面对选出的8个分析类。前面的四个问题帮助我们缩小了搜索范围,是个很有效的工具。
但是我们有没有犯错呢?有没有漏过某个真正的类,或者把一个不该作为类的词加进来了?这并不重要。
我们现在完成了RUP的用例分析活动中的前三步:
如果我们严格按照RUP过程进行,下一步应该是:
基于以下理由,这一次,我又会对RUP过程做一点小的改动:回顾一下我们的进展。我们刚刚找到了8个实体类,我们认为这些都是我们系统中的类。在我们做下一步之前,我们需要给这8个实体类增加一些内容,来确认他们是类。
有三种基本的方法来充实我们的分析类:
数据驱动的方法对于从事数据库工作,或者从事过程语言编程的人员来说很常见。他们就是用数据和数据之间的关系来认识、
行为驱动的方法有着双重的成立理由。首先找出类执行的操作,从中决定这些操作涉及的数据中,哪些应该被这个类所拥有。这很棒,但是我们如何确认我们为类找出的操作之间能够保持一致呢?如何区分操作和类,以明确这个操作是属于这个类,但是 其它的操作要属于同一个类,还是其它的类?我们需要某种方法来区分各个操作。这就是职责驱动的方法带给我们的。
职责驱动的方法是自上而下的,从总体的类及其职责的视图开始。首先定义一个类在业务中的“使命”,这个“使命”描述了这个类对外提供的服务。从军事术语上来说,就是责任和策略。操作和数据是战术层面上的(为战略服务的,服从于某些目标、策略的)。
一旦我们确定了我们的类的职责,我们就可以设计一个分析类图,来描述类间关系的整体结构。这个结构一般都是由业务领域决定的。一个UML分析类图可以帮助我们可视化的把这个关系的结构表示出来。
因此,我建议对标准的RUP过程做如下修改(次序的改变):粗体):
在这里,我们要对每个分析类进行处理。类的职责描述了这个类在系统中所提供的服务,而且其它类不会重复提供这些服务。各个类的职责不能重叠。
根据我们对汽车租借领域的理解,和对汽车租借专家、业务分析专家一起工作,我们在表3中,写出了我们对每个分析类的职责的理解。
James Rumbaugh与其它人
如果我们定义的职责出现错误怎么办?我们再重复一次,这无关紧要。我们已经开始,并且在不断取得进展。当我们对系统了解的更多之后,我们对类的职责作出些调整是很正常的。这样才能帮助我们把建模工作和软件做的更好的。
我们已经确定了类的职责,下面将会设计一张UML类图作为起点,来找出分析类之间的关系。 建立类图有四个简单的步骤:
通过以上步骤,加上我们前面确定的类的职责,我们得到了UML类图,如图6:
这个类图上有三种UML关系,用不同的线区分出来。简单的实线表明是关联关系。用来表明两个类之间的点对点的关系,每个类都会调用另一个类提供的操作。
在线上用实心菱形表示,在预约和保护产品之间的是合成关系(或者说不可共享的聚合关系)。
在线上用空心菱形表示的,在汽车租用和汽车之间的是聚合关系(或者说,可共享的合成关系)。这也是个整体/部分的关系,或者说是一种拥有的关系,但是当我们销毁汽车租用的时候, 我们并不销毁汽车。这符合常理:因为汽车不出租时, 汽车对象会暂时成为“孤儿”,但是并不被销毁,只是把它提供给另一个汽车租用。
在关系两头的数字和* 符号叫做多样性描述。这些符号表明有多少个实例可以被连接到
在分析中,我们试图确认,我们能够正确的表述和理解问题。对业务专家和分析专家来说,分析类图是一个有用的工具,帮助他们和技术人员一起审查设计,并且将设计进一步推进。
现在我们有了类,职责和一张显示类间关系的结构的类图。但是迄今为止,我们还没有涉及类的内部-没
这些类如何协作完成预定一辆汽车这个用例?我们用UML交互图来找出分析类之间的这些交互动作。回忆一下前面提到的顺序图和协作图,就是两种交互图,它们是我们的用例实现的一部分,如图7所示,这是一张分析级别的顺序图,描述了预定一辆汽车这个用例。
你会看到,在这个图中我已经引入了一个非业务类-UCController。这个用例控制类表示的
顺序图和交互图包括几乎相同的内容,它们只是表示方式不同而已。选用哪一种图主要取决于是否方便和个人偏好。在顺序图中,对象按竖列对齐,按照从上到下的时间线来顺序排列。标了数字和文字的水平线叫做消息。
顺序图与协作图相比,有一个非常明显的优点,就是在图左边的脚本。这些文字是从用例,或者场景中摘取的,对顺序图的描述。这些描述就是对预约汽车这个用例的一个简短的描述。通过把这些脚本放在图边,使得消息的含义变得非常清晰。而且把消息、对象和原先的用例相结合起来。用例中的每条语句都会对应图中的一个或多个消息,在顺序图上,这表示的非常清楚。
我想强调一下,在分析阶段的交互图中很重要的一点就是:消息表明了意图,而不是实现,也不是接口在预约汽车顺序图上,消息只是简单的表明了,我希望接收消息的对象做什么事情,消息并不代表一次函数调用。函数调用这些更具体的信息,会在设计阶段确定。但是现在,我们只需要在这个用例中,类的职责。
我们如何找出这些消息呢?只要看看我们的职责的定义就可以了。例如,在第八步中,租借地点要决定在
在顺序图中,对象框没有名字“:<classname>”,这些对象叫做匿名对象。但是也可以为对象起一个名字。如果我们有一个类叫做Account,我们可以这样命名:
如果我们建立两个Account类的实例对象,FredsStash和EthelsMadMoney,它们将是这样:
以左边的一个为例,表明“FredsStash是Account类型的一个对象”。
在分析中,我们会发现,类为了完成自己的职责,会需要一些属性(也就是类的属性变量)。从类的职责列表中,我们可以确定分析类的一些属性。另外一些属性要从常识中得出(例如,每个汽车对象应该有一个独一无二的标识属性,与实际的汽车上的汽车联盟的标准汽车编号相对应)。
UML 说明:UML中的类在图上分为三段,以Account类为例,如下图所示:
图8中的类图上展示了汽车租借的分析类、它们之间的关系以及每个类所拥有的一些基本的属性。这些属性是由类的职责推理得到的一些很明显的属性。请注意这些属性都没有表明数据类型,因为数据类型是设计阶段的问题。
在当前这一步中,我们只需要表明顾客类具有一个叫做地址的属性就足够了。至于地址这个属性
分析机制指的是高级的系统构建组件,它可以提供解决特定领域问题所需要的一些服务,而不是技术方面。例如,在保险领域,保单中的信息、声明和其它内容,在整个保险管理期间都是需要的。这个需求用分析机制来说,就叫做:持久化:无论程序是否运行,都一直维护数据的信息和状态。请注意我们并没有指定使用Oracle SQL,或是SQL Server这些特定的实现环境,我们只是列出了持久性,和我们后面会谈到的设计机制和实现机制。实现机制将会是与特定平台或者软件供应商相关的。
我们在表4中试着举例说明,分析、设计和实现机制之间的关系:
一些通用的分析机制是:
在汽车租借系统中,我们需要为这些指定分析机制:
在“从用例到代码”的第一部分中,我们从一个用例开始,迄今为止已经找出了用来实现用例的类,它们之间的关系,和它们需要的属性。我们还找出了一些分析机制,在今后的设计和实现中会用到它。
如果我们对另一个用例再一次重复这整个过程,我们会发现另一些分析类,定义它们的职责,它们之间的
我们已经完成了很多工作,但是我们还无法开始写代码。下面我们的重点将会放到用例设计上面来,这就是本文第二部分的主题。
感谢IBM Rational的Peter Eeles和Zoe Eason,他们对本文的早期版本提出了深刻的见解和建议。
Wirfs-Brock, Rebecca. Designing Object-Oriented Software. Prentice-Hall,
Rumbaugh, Jim,
The Rational Unified Process, version 2003.06.00.65. |