用例分析

自从1992年 Ivar Jacobson 发表了关于如何使用用例,从系统用户的角度来提取软件需求的方法的论文之后,这种方法已经逐渐流行起来。但是有一个最常见的问题是:当我得到了用例之后,如何才能把他们用代码实现出来?本文由两部份组成,将会用一个实际的案例来说明这一点。如何从用例中提取需求,并加以分析,进一步将其转化为可以直接进行编码的格式。我希望能够把这一过程讲清楚,这样你在当前的,或是下一个软件项目中就可以使用它们了!

IBM Rational Unified Process(RUP) 提倡通过用例来提取系统中可操作的需求。 1软件需求说明书,即 Software Requirements Specification (SRS),包括软件的所有需求,其中包括很多相关的文档。用例就是其中的一个重要部分。 SRS主要包括以下几部分:

  • 用例模型,包括:

    1. 用例图:可视化的描述系统用户,和系统为用户提供的服务。

    2. 角色定义:用文字描述系统功能,和系统所需的服务。

    3. 用例描述:用文字描述系统提供的主要功能。

  • 软件补充规格文档:这个文档包括了整个系统相关的各种需求,和一些与对系统用户和用例都不直接相关的隐藏需求。

在RUP方法中,这个需求文档是后续的分析和设计工作的起点。不同的开发方式,对项目的推动力是不同的,也就会产生不同的文档。如果软件发布之后,你还总是在不停的修补缺陷,你很可能根本没有需求文档。只能从Bug报告中看出,软件已经和最开始设计的样子不一样了。如果你在维护或者改进一个软件版本(比如增加一项新功能),你可能有一两个用例,他们描述了这些新功能和用户之间如何交互,但是你不会有软件补充规格文档,因为这些功能之外的部分并没有改变。

在这里,用一个虚构的软件项目"green-field" 作为例子。这是一个面向对象的软件项目,使用UML来描述系统中的各种概念与关系。读者需要具有对象和类的基础知识,熟悉UML 版本1.x 或者2.0中的类图、顺序图和协作图。

用例分析活动

下面我们讨论一下RUP中的用例分析。如图1所示,将RUP中结构分析的结果整合起来。

图1: 结构分析的工作流程(early Elaboration)

显而易见,严格来说的话,软件开发过程应该侧重于企业系统级的架构设计,以及软件重用。但是基于以下三点原因,在这里我不会长篇大论的讲述结构分析:

  1. 我的目标不是结构分析设计,而是面向开发人员的更底层的日常工作。

  2. 这不是一本专著,没有那么多篇幅来讲述结构分析。

  3. 根据我多年在软件架构和软件过程方面的咨询经验来看,只有很少的软件开发组织能够很规范的进行结构分析。如果你正在从事结构分析,你一定曾经经历过本文中的一些内容。对一个新的,或是一个大型的软件项目来说,采用结构分析是明智的选择。但是如果你不太熟悉结构分析,本文的内容将会对你有所裨益。

用例分析的目的:

  • 找出用例中的执行流程、事件的各个类。

  • 通过实现用例,把用例的行为指定到具体的类。

  • 找出类的责任、属性和他们相互的关系。

  • 规范地确定系统中各用例的职责。

我们也可以认为,用例分析的目标,就是把我们对用例的理解,转变为与业务一致的形式,实现需求的价值。在用例设计的时候,我们把业务概念抽象成类、对象、关系、组件、接口等等,这些都与目标系统直接对应。

图2引自RUP分析和设计概述部分,描述了需要使用用例分析的场景,和用例分析与其它步骤之间的关系。

图2:RUP中的用例分析

在RUP [RUP2003]中的用例分析由几部分组成:

  • 每个用例包括:
    1. 建立一个用例实现

    2. 用例的补充描述(如果需要)

    3. 从用例的行为中,找出分析类(Analysis Classe)

    4. 把行为指定到具体的分析类

  • 对每个分析结果的类:
    1. 类的职责

    2. 类的属性和关系
      • 定义类的属性

      • 分析类直接的关系

      • 分析类间事件与事件之间的相关性
  • 整合所有的用例实现

  • 建立跟踪机制

  • 建立分析机制

  • 对用例分析的结果进行评估

请注意这些步骤的次序不是一成不变的。 你实际所采用的次序取决于你对于分析的领域的理解程度、你对RUP或UML的经验、你所采用的模型、或者是你自己的确定分析类的属性的一种习惯方式。(例如,以职责为中心,以行为为中心,或者以数据为中心) 关键只在于你是否能够对问题建立一个准确的描述,而不是对我们的用例设计的准确描述--这将是本文的第二部分的内容)。我会遵循本文中的大多数(但不是全部)步骤,而且我会改变一些步骤的次序。在每个步骤的具体内容中,我会说明为什么这样做有助于RUP和OOAD的新手更好的学习OOAD。

如图3所示,一些步骤把编写用例和实现代码分隔开了。还列出了RUP中用例分析部分推荐使用的一些步骤。这张图将会指出在后面部分中,我们的前进方向。

图3:用例分析的步骤


用例的具体实例

例子可以更好的说明问题。这个虚构的简短例子是一个汽车租赁公司的网站系统。这种系统一般有几个用例描述了为客户提供的服务,如:

  • 预约汽车

  • 取消预约

  • 查看租车历史信息

  • 查看/编辑顾客档案

  • 加入酬宾活动等

为了简化模型,假设我们的服务只针对个人用户,不针对企业用户。

为了让例子尽量简单、易于理解,我们只分析其中的一个用例。描述如下:预约汽车.

用例:预约汽车。
  1. 这个用例从顾客提出他想要预约一辆汽车开始。

  2. 系统提示顾客输入希望的租借和归还汽车的时间、地点,顾客输入以上信息。

  3. 系统提示顾客选择希望租用的车型,顾客进行选择。

  4. 系统列出在指定时间、地点可用的,所有符合条件的汽车。如果顾客需要某辆汽车的更详细的信息,系统也可以提供。

  5. 如果顾客选定了某辆汽车,系统提示顾客输入个人信息:姓名、电话、电子邮件等等,顾客输入以上信息。

  6. 系统提供各种保护产品的信息(如汽车损伤保险、乘客险等)并询问顾客是否购买,顾客做选择。

  7. 如果最后顾客表示接受这个预约,系统通知顾客预约已成功,给顾客一个确认。

  8. 当确认信息出现时,这个用例就结束了。

必须用通俗易懂的方式来描述用例,不仅仅对web程序是这样,即使是顾客走到营业点去当面预约也是一样。 用例描述只需要指出结果,不需要详细说明具体系统将怎样操作、系统的客户将怎样操作。如果你用“营业员”代替上面的“系统”一词,上面的描述就变成了一段对顾客到营业员处预约汽车的准备描述。其中的第7步给出确认的手续,在这里就变成了书面格式的确认。

在设计web界面时,可以把多个步骤放在同一个页面中,例如上面描述中的步骤2和3。在web环境中,第7步的确认,将会在预约交易报告页面中呈现给顾客。

也请注意用例描述的文字风格。上面的描述是用正面的语气,用现在时态来描述的。正面的语气指的是描述清晰果断,相反的负面的语气指的是被动的语气,和比较含混的描述。例如,“John投球”是正面的语气,动作的执行者John放在动词前面。这句话的被动语气的描述是“球被John投出去”,或者更简单的“球被投”,连动作主体也省略了。动作的执行者John放在动作后面。在所有的负面语气的描述中,动作的执行者会出现在介词短语中。让你的用例描述清晰、一致。使用正面的语气,现在时态。不要使用太多的词汇,描述清晰就可以,不要使用太多无关的词语,保持前后一致。例如,不要使用“顾客”,而是使用“客户”,或者“商业伙伴”也不错。如果同时使用这三者,你的读者会认为你是在描述3种不同的用户,他们的信息和权限也不同。

我们准备好的用例描述,可以作为下一步的起点。下面让我们开始按照RUP中的用例分析过程来开始分析。


用例分析第一步:建立一个用例实现

RUP中的用例分析过程的第一步是建立一个用例实现。在我们给出用例实现的定义之前,回过头来看一下,到底什么是用例?我们需要怎样来确认用例?我们写好的用例,是一个业务过程的描述,描述了顾客预约汽车的过程。它说明,我们会按照一些固定的步骤,按照固定的业务规则(例如在得到顾客的姓名之前不进行任何处理),也不为顾客提供那些无法按照指定的时间地点供客户租用的汽车的信息。

由于我们是在设计一个面向对象的软件系统,我们的用例行为需要由我们系统中的一些对象和类来执行。但是迄今为止,我们还没有任何对象和类,因此我们需要从用例中去发现这些对象和类。还需要指定每个类要执行用例图中的哪些行为。

如图4所示,用例实现实际上是一组UML图,说明了我们都需要哪些类、它们的职责和它们的实例对象之间如何进行交互,来完成用例中的行为。

图4: 机票预订系统中的一个RUP用例实现

通常一个用例实现会由下面这些组成:

  • 包括我们所关注的用例中出现的所有类的一个UML类图(有时也叫做合作类视图), 和

  • 描述交互的对象,以及它们之间的调用关系的一个或多个UML交互图 。UML定义了两种类型的交互图:顺序图 (如图4),和协作图。都很有效。

看起来我们有很多事情需要处理,是这样吗?是的,而且当你使用诸如Rational Rose或Rational XDE这样的开发工具的时候,这项工作看起来更像一项家务管理工作,你只需要建立很多结构,存放你的用例实现。后面我们会完成实际的类和交互图。但是现在我们先来明确一下我们的目标:一个类图,一个或多个交互图。


用例分析第二步:补充用例描述

当你在进行分析的时候,你的用例描述只记录了从系统外面的用户角度来看,系统的行为是什么样子的。在概要的描述系统内部的一些不可见的操作的时候,这足够了,但是按照这样的描述,并不能完成具体的实现。

举一个例子,看看上面描述的第四步:系统列出在指定时间、地点和可用的所有符合条件的汽车。如果顾客需要某辆汽车的更详细的信息,系统也可以提供。在这里,我们有可供搜索汽车信息的数据库了吗?我们也许知道,汽车信息由CICS(客户信息控制系统)管理,存放在MVS主机上,通过LU6.2 APPC可以访问,但是我们不能确定这一点。让我们来把这些我们要做的事情,特别是在我们系统之外事情的细节,来清楚的确定下来,而不是只知道我们希望怎样做。下面是补充了这些信息的第四步,补充一个汽车数据库: 系统通过访问这个汽车数据库来查找汽车的位置信息,并列出在指定时间地点和可用的所有汽车。

这里我们给出了一个汽车数据库的信息,并且比较抽象的描述了如何用网页来访问它。这是一个很简单的例子,但是读者可以从中了解一些内容。

在RUP这样的迭代开发流程中,从分析阶段转移到设计阶段只用了很少的时间。对一个中等规模的,四周左右的项目来说,你可能第一周用来获取需求, 做你的分析和设计,然后后面3周都用来写代码和进行测试。你用于分析的用例会侧重于系统对外可见的行为,不过你可能需要细化这些用例的描述,增加更多的系统内部如何交互的描述。这样你的顾客和分析人员才能确认你没有遗漏一些重要的业务。请注意,只需要把用例描述细化到你可以很好的找出系统中的分析类的程度就可以了。 对于设计级别的类(诸如树、堆栈、队列、集合等等)可以在后面的阶段完成(如设计阶段)。

例子:补充“预约汽车”用例

假设我们的系统是一个网站程序。当客户需要的时候,我们会为他们提供私人在线预约汽车服务。我们要为这个实现的具体情况来细化我们的用例,不过不要过多的涉及到设计阶段的工作。

这是预约汽车用例的更详细的描述,还是侧重于做什么,而不是侧重于怎么做

用例:预约汽车(补充)
  1. 这个用例从顾客进入我们的预约汽车网站开始。

  2. 系统提示顾客输入希望的租借和归还汽车的时间、地点,顾客输入以上信息。 系统还提供给顾客一个选项,来选择汽车的种类,如微型汽车、SUV、标准汽车,等等。顾客可以限定在一个或多个种类中搜索汽车。默认值是搜索所有种类的汽车。如果顾客参加了我们的有奖租赁汽车活动,他可以在网页上一个单独的地方输入他的有奖活动的编号。如果他填写了这个编号,系统会访问顾客的档案, 来预先获取相关的信息。

  3. 如果顾客要继续进行预约,系统在下一个网页上,列出从数据库中找到的所有符合条件(时间地点)的汽车,提供每辆汽车的基本费率,根据顾客的租用历史情况还可以打一点折扣。如果顾客需要汽车的更详细的信息,系统从数据库中查找该信息,并将其显示给顾客。

  4. 如果顾客选定了一辆要预约的汽车,系统在一个新的网页中让顾客填写个人信息(姓名、电话、用于确认的电子邮件、信用卡发行商,等等)。如果系统中已经有该顾客的档案,则调出系统中的相应信息显示在页面上。有些信息是必填项,另外一些则是选填项(如电子邮件)。用户需要填写所有的必填项。系统还提供各种保护产品的信息(如汽车损伤保险、乘客险等)和单日的价格,并询问顾客是否购买,顾客做选择。

  5. 如果顾客表示“接受这个预约”,系统在网页上显示这次预约的概要信息(汽车型号、日期、时间、选用的保护产品及其费用、费用总额等等),让顾客确认。如果顾客填写了电子邮件,系统会发送一封确认信到顾客的电子邮件地址。

  6. 当确认信息出现在顾客面前时,这个用例就结束了。

在这个经过补充的用例描述中,我们清晰的描述了一个基于浏览器的程序的行为,描述了很多对顾客来说不可见的行为。但是并没有提供设计级别的信息。

我们需要为每个用例提供类似的详细信息吗?

不需要。但是请记住,补充细节,并不是指实现的具体细节。我们的目标是为了能够有足够的信息来理解系统中的分析类,并与你的顾客或分析人员就所有用例的业务流程达成一致。 如果你觉得补充一些细节对于找出系统中的分析类有所帮助,那么就加上它。

注意:把握这个细节的尺度会比较困难。需要时间和一些练习。多上手练习,向别人多询问,还要记住当你无法决定的时候,应该稍微偏向抽象一点的描述。增加一些没有写上去的内容比较容易做到,但是要从很多杂乱的描述中找到有用的信息可就难的多了。

为什么还要先写出比较抽象的用例呢?为什么不直接把用例补充的比较详细?

这是因为抽象的用例是系统行为的最通用的描述(不过多的描述系统内部行为)。如果你还要开发一套客户端/服务器版本的预约汽车的用例会怎么样呢?如果你从详细的用例(基于浏览器的系统用例),每次改变你的实现平台的时候,你都得重写整个用例。这个通用的描述与技术无关,特别是当你还没有、或是无法确定系统的具体环境的时候,它的价值就得到了体现。另外,通用描述用例能够让你的业务分析人员能够只看到系统如何工作的内容,而不是看到包含很多他们难以理解的技术细节的内容。


用例分析第三步:为用例行为找出分析类

根据RUP过程,这一步的目的是找出分析类的候选范围,这些类合作起来,可以完成用例的所有行为。因为我们还没有任何类,所以我们的主要目标就是找出在汽车租借系统中的所有分析类。

这就引出了一个有趣而又重要的问题:什么是分析类?有两种答案。首先,一个业务级别的分析类是业务领域中的一个要素,与实现技术无关。例如,银行系统中的银行顾客、帐户、帐号交易等等。而且它与系统的实现无关,不管是一个新的电子商务系统,或是一个从19世纪80年代就开始使用的借贷系统。

其次,RUP过程把这个定义扩展出三种不同的分析类:实体类控制类边界类。RUP过程中的实体类大致上相当于前面提到的,业务级别的分析类。控制类与业务过程相关,它们控制整个业务的流程和执行次序。它通常控制一个用例中业务过程的执行。边界类在系统与外界之间,为它们交换各种信息与事件。边界类处理软件系统的输入与输出。

根据我在面向对象技术和面向对象建模方面的教学经验,我发现开发人员总是很快的从RUP过程中的实体类、控制类和边界类这个环节,进入下一个设计环节, 没有对问题进行足够的分析。实际上,显然控制类和边界类都是面向技术实现的类,而不是面向业务的类。它们都是在设计阶段所定义的系统设计模型中的一部分,而不是分析模型。因此,在这一步,我将会侧重于业务方面的,与技术无关的分析类。在讨论设计的时候再讨论涉及技术的部分。请一直记住,搜索与业务相关的类,是在RUP过程中的结构分析部分进行的,如果你的项目采用了RUP过程的话。

让我们回顾一下,用例描述的是行为,也就是系统为用户提供了什么样的服务。在用例描述中,没有涉及面向对象的内容,但是这些描述是用来找出系统中的对象和类的。可以在很多地方,用各种各样的方法来寻找类:

  • 领域的常识只是

  • 前一个类似的系统

  • 企业模型或供参照的体系结构

  • CRC (类/职责/合作关系)方法

  • 词汇表

  • 数据挖掘

寻找类的一个简单的方法就是语法分析,下面我将加以说明。我们只要找出我们的需求中的名词, 这些名词(有些是形容词+名词):

  • 一些是类。

  • 一些会成为类的属性。

  • 一些对我们的系统无关紧要。

找出预约汽车的用例描述中的这些名词(跳过代名词,如“他”),如下:

用例:为顾客预约汽车(补充)

  1. 这个用例从顾客进入我们的网页开始。

  2. 这个系统显示输入框,来提示顾客输入借出和归还时的预约地点,和借出和归还时的日期时间顾客输入地点日期系统还提供选择框,让顾客来限定汽车型号分类 ,例如,如微型汽车、SUV、标准汽车,等等。顾客可以指定一个汽车分类进行搜索,也可以指定多个分类默认的设置是在所有的 汽车 分类中搜索。如果顾客参加了我们的租赁有奖活动,他需要把他的有奖活动中的编号输入到网页上的一个独立的输入框中。如果这个输入框 填写了内容,系统会访问顾客的个人档案系统会提前查找所需的 信息

  3. 如果顾客表示希望继续进行预约过程系统访问汽车数据库,查找位置信息然后在一个新的网页中显示所有的汽车信息。这些汽车都是在指定的汽车分类 中的,并且在指定的地点日期时间中是空闲的。对每辆汽车系统都会显示一个基础费率,如果顾客租用汽车的历史比较长,还可以打一些折扣。如果顾客想要看到某辆汽车更详细的信息系统汽车数据库中读取这些信息,并把他们显示给顾客

  4. 如果顾客选定了汽车来预约,系统显示一个新的网页,提示顾客输入用于确认顾客身份的个人信息,(姓名, 电话, 用来确认电子邮件, 信用卡发行商 )。 如果顾客档案已经存在了,系统从中读取所有已经填写过的信息。一部分输入信息是必填项,其它(如电子邮件)是选填项。顾客填写所有必填的信息系统还要显示保护产品相关的信息(如汽车损伤保险、乘客险等)和单日的价格,并询问顾客是否购买这些保护产品顾客给出决定

  5. 如果顾客表示接受这个预约系统给出一个网页,来显示 预约的摘要信息,(汽车的类型、 日期时间、所有选购了的保护产品及其费用租用总额),还为顾客显示预约的确认页面。如果 系统有用户的电子邮件系统会发一封预约确认信到这个地址

  6. 这个 用例预约确认信息出现在顾客面前的时候,就结束了。

注意每个名词,或者形容词+名词的组合, 都被标出来了。有很多重复的,因此把每个词单独列入词汇表1,按字母顺序排序:

表1:候选的名字/实体类

我们如何分辨出哪些候选的名词才是真正的问题领域中的类呢?一个常用的方法就是,用一些简单的问题来测试每个词,如图5:

  1. 这个候选是在系统的边界之内吗?
    如果不是,它可能是系统的用户。

  2. 这个候选词有某些明显的与业务主题有关的行为吗?
    (也就是说,这个候选词可以拥有或者提供某些系统的服务或功能吗?)

  3. 这个候选词拥有明显的数据结构吗?
    (也就是说,这个候选词拥有或者管理某些数据吗?)

  4. 这个候选词和其它候选词之间有什么关系吗?

图5:用来寻找分析类的问题

如果某一个答案是“不是”,那么这个候选词很可能不是一个类。再继续检查下一个词。如果答案是“是的”,那么继续检查下一个问题,如果所有的问题的答案都是肯定的,这个候选词就是一个类。再继续检查下一个词。

我们对每个候选词做检查,就会得到类似于表2的结果:

表2:名词检查结果

请注意租借地点已经被加进去了,虽然它不是用例的一部分。在与我们的业务专家(Subject Matter Experts,SMEs)们谈话时我们发现,业务中会使用 地点来指代一个地址,或者一个租借部门。为了不致于混淆,我们一致统一使用单词租借地点来表示业务地点,也就是发生租借和归还的地方。

从这个列表中,我们列出了通过了问题检查的词,也就是下面列出的分析类

啊哈!在总共得30多个候选中,现在我们只需要面对选出的8个分析类。前面的四个问题帮助我们缩小了搜索范围,是个很有效的工具。

但是我们有没有犯错呢?有没有漏过某个真正的类,或者把一个不该作为类的词加进来了?这并不重要。RUP的迭代特性,会逐渐暴露出我们犯过的错误,并允许我们用尽量小的代价来修正它们。分析和设计的目标不是先把一切事情设计的很完美,而是当你需要确认这些事情的时候,才去确认这些。开始阶段往往是最困难的,我们现在实现了对象(或者说类)从无到有的突破!重要的是我们已经起步了,而且我们能够按照面向对象的方法一步一步的取得进展。

我们现在完成了RUP的用例分析活动中的前三步:

  • 对每个用例

    1. 建立一个用例实现

    2. 补充用例描述(如果需要的话)

    3. 从用例行为中,找出分析类

如果我们严格按照RUP过程进行,下一步应该是:

    1. 把行为分配给分析类

基于以下理由,这一次,我又会对RUP过程做一点小的改动:回顾一下我们的进展。我们刚刚找到了8个实体类,我们认为这些都是我们系统中的类。在我们做下一步之前,我们需要给这8个实体类增加一些内容,来确认他们类。

有三种基本的方法来充实我们的分析类:

  • 数据驱动的方法

  • 行为驱动的方法,和

  • 职责驱动的方法。

数据驱动的方法对于从事数据库工作,或者从事过程语言编程的人员来说很常见。他们就是用数据和数据之间的关系来认识、描述世界的,因此会首先去寻找类中的数据-一般没有什么标准的方法去寻找类的函数或功能。这看起来不错,但是数据只是工作的一半。实际上, 类的准确定义,包括了数据和对数据进行的操作。

行为驱动的方法有着双重的成立理由。首先找出类执行的操作,从中决定这些操作涉及的数据中,哪些应该被这个类所拥有。这很棒,但是我们如何确认我们为类找出的操作之间能够保持一致呢?如何区分操作和类,以明确这个操作是属于这个类,但是 其它的操作要属于同一个类,还是其它的类?我们需要某种方法来区分各个操作。这就是职责驱动的方法带给我们的。

职责驱动的方法是自上而下的,从总体的类及其职责的视图开始。首先定义一个类在业务中的“使命”,这个“使命”描述了这个类对外提供的服务。从军事术语上来说,就是责任和策略。操作和数据是战术层面上的(为战略服务的,服从于某些目标、策略的)。 2

一旦我们确定了我们的类的职责,我们就可以设计一个分析类图,来描述类间关系的整体结构。这个结构一般都是由业务领域决定的。一个UML分析类图可以帮助我们可视化的把这个关系的结构表示出来。

因此,我建议对标准的RUP过程做如下修改(次序的改变):粗体):

  • 对每个分析类

    1. 描述类的职责

    2. 在分析类图上,建立分析类间的联系

    3. 把行为指定给分析类(找出操作)

    4. 描述每个类的属性和关系

      • 定义类的属性

      • 描述分析类间事件的相关性

用例分析第四步:描述类的职责

在这里,我们要对每个分析类进行处理。类的职责描述了这个类在系统中所提供的服务,而且其它类不会重复提供这些服务。各个类的职责不能重叠。

根据我们对汽车租借领域的理解,和对汽车租借专家、业务分析专家一起工作,我们在表3中,写出了我们对每个分析类的职责的理解。

表3:每个分析类的职责

James Rumbaugh与其它人 3定义一个对象,或者说类,作为“一个概念,抽象,或者一个对业务来说有意义的,具有清晰定义的东西【我的重点】”。在类的职责定义中,首先要注意“清晰定义”,就是指明确指定了可以做什么,不可以做什么。

如果我们定义的职责出现错误怎么办?我们再重复一次,这无关紧要。我们已经开始,并且在不断取得进展。当我们对系统了解的更多之后,我们对类的职责作出些调整是很正常的。这样才能帮助我们把建模工作和软件做的更好的。


用例分析第五步:建立分析类之间的关系

我们已经确定了类的职责,下面将会设计一张UML类图作为起点,来找出分析类之间的关系。 建立类图有四个简单的步骤:

  1. 确定要进行建模的类(我们已经完成了)。

  2. 确定哪些分析类具有与其它类之间的关系。

  3. 对于任意两个之间具有关系的类,确定关系的语义 :是关联、聚合、合成还是继承?

  4. 对于非继承关系,确认关系的多样性(指另一个类的多少个对象可以被关联到这个类的一个对象上面,类似于数据建模中的概念。)

通过以上步骤,加上我们前面确定的类的职责,我们得到了UML类图,如图6:

图6:汽车租借系统的类图

这个类图上有三种UML关系,用不同的线区分出来。简单的实线表明是关联关系。用来表明两个类之间的点对点的关系,每个类都会调用另一个类提供的操作。

在线上用实心菱形表示,在预约和保护产品之间的是合成关系(或者说不可共享的聚合关系)。这是个整体/部分的关系,或者说是一种拥有的关系。在这张类图上,合成关系表示预约拥有管理0个或者多个保护产品,这些保护产品被预约所拥有。更进一步来说,合成关系表明如果预约这个对象被销毁了,他所拥有的保护产品也必须被销毁,因为当保护产品不再是预约的一部分之后, 就失去了存在的意义。

在线上用空心菱形表示的,在汽车租用和汽车之间的是聚合关系(或者说,可共享的合成关系)。这也是个整体/部分的关系,或者说是一种拥有的关系,但是当我们销毁汽车租用的时候, 我们并不销毁汽车。这符合常理:因为汽车不出租时, 汽车对象会暂时成为“孤儿”,但是并不被销毁,只是把它提供给另一个汽车租用。

在关系两头的数字和* 符号叫做多样性描述。这些符号表明有多少个实例可以被连接到一个实例上。例如可以和一个顾客有关联关系的汽车的数量。或者,反过来,可以和一辆汽车有关联关系的顾客的数量。在这个类图里面,我们的多样性表明“对每位顾客,可以没有汽车预定,也可以有多项汽车预定”。反过来,就是“对每辆汽车,可以没有顾客预定,也可以有多个顾客预定”。

在分析中,我们试图确认,我们能够正确的表述和理解问题。对业务专家和分析专家来说,分析类图是一个有用的工具,帮助他们和技术人员一起审查设计,并且将设计进一步推进。

现在我们有了类,职责和一张显示类间关系的结构的类图。但是迄今为止,我们还没有涉及类的内部-没有操作和属性。而且类图是静态的。我们如何确认这些类能够完成我们在用例中描述的业务过程?这就是下一步的目的,这是一个非常重要的步骤,因为它将会把用例描述映射到分析类的潜在的操作上面。


用例分析第六步:确认分析类的行为

这些类如何协作完成预定一辆汽车这个用例?我们用UML交互图来找出分析类之间的这些交互动作。回忆一下前面提到的顺序图和协作图,就是两种交互图,它们是我们的用例实现的一部分,如图7所示,这是一张分析级别的顺序图,描述了预定一辆汽车这个用例。

图7: 预定一辆汽车的分析阶段的顺序图

点击放大

你会看到,在这个图中我已经引入了一个非业务类-UCController。这个用例控制类表示的是一个尚未进一步定义的类,它的职责是从用户那里接收事件和消息。我发现大多数读者都会感到困惑:一个业务类(例如出租地点或者预约)来接收用户的消息?因此我通常会给我的分析交互图增加一个通用的用例控制类,来表示这个逻辑,而且方便了读者的理解。在设计阶段,我们会把这个类改名为ReserveAVehicleController,但是现在我用这个通用的名字UCController 来表示。

顺序图和交互图包括几乎相同的内容,它们只是表示方式不同而已。选用哪一种图主要取决于是否方便和个人偏好。在顺序图中,对象按竖列对齐,按照从上到下的时间线来顺序排列。标了数字和文字的水平线叫做消息。在一个顺序图中,消息的顺序用它们的位置来表示:按照时间顺序从上到下排列,因此排在下面的消息就在排在上面的消息之后发生。消息从一个对象的时间线上开始,在一条时间线上终止(一般都是另一个对象的时间线),但是有时也会终止在同一个对象的时间线上,如图7中的消息21。

顺序图与协作图相比,有一个非常明显的优点,就是在图左边的脚本。这些文字是从用例,或者场景中摘取的,对顺序图的描述。这些描述就是对预约汽车这个用例的一个简短的描述。通过把这些脚本放在图边,使得消息的含义变得非常清晰。而且把消息、对象和原先的用例相结合起来。用例中的每条语句都会对应图中的一个或多个消息,在顺序图上,这表示的非常清楚。

我想强调一下,在分析阶段的交互图中很重要的一点就是:消息表明了意图,而不是实现,也不是接口预约汽车顺序图上,消息只是简单的表明了,我希望接收消息的对象做什么事情,消息并不代表一次函数调用。函数调用这些更具体的信息,会在设计阶段确定。但是现在,我们只需要在这个用例中,类的职责。

我们如何找出这些消息呢?只要看看我们的职责的定义就可以了。例如,在第八步中,租借地点要决定在这个地点,哪些汽车是可用的。在第九步中,汽车租借要提供在这个地点,能够满足用户要求的日期和时间的所有汽车。在第十步,租借中的每个汽车都需要回答它本身是否满足某些租借条件。请注意所有这些知识都不在租借地点这个对象中。我们把这些知识分布到了我们的所有分析类当中,因此每个类都可以按照定义来完成一部分工作。

UML说明:对象-是否需要命名?

在顺序图中,对象框没有名字“:”,这些对象叫做匿名对象。但是也可以为对象起一个名字。如果我们有一个类叫做Account,我们可以这样命名:

Account

如果我们建立两个Account类的实例对象,FredsStashEthelsMadMoney,它们将是这样:

FredsStash : Account EthelsMadMoney : Account

以左边的一个为例,表明“FredsStash是Account类型的一个对象”。 如何决定是否给一个对象命名呢?如果系统中的一个实体类,有一个很清楚的名字,你可能想为它的对象起一个名字。如果你的图中有一个示范的对象(类似于数据模型中的数据表例子),你也可以用一个有名字的对象。但是对大多数情况来说,匿名对象就足够了。我们只关系类和对象提供了什么服务,致于对象的名字并不会影响到对象的行为。


用例分析第七步:描述属性和关系

在分析中,我们会发现,类为了完成自己的职责,会需要一些属性(也就是类的属性变量)。从类的职责列表中,我们可以确定分析类的一些属性。另外一些属性要从常识中得出(例如,每个汽车对象应该有一个独一无二的标识属性,与实际的汽车上的汽车联盟的标准汽车编号相对应)。

UML 说明:UML中的类在图上分为三段,以Account类为例,如下图所示:

图8中的类图上展示了汽车租借的分析类、它们之间的关系以及每个类所拥有的一些基本的属性。这些属性是由类的职责推理得到的一些很明显的属性。请注意这些属性都没有表明数据类型,因为数据类型是设计阶段的问题。

图8: 类属性的起点

在当前这一步中,我们只需要表明顾客类具有一个叫做地址的属性就足够了。至于地址这个属性是什么样的,甚至需要不需要成为一个独立的地址类,会在后面的阶段中决定。你会发现汽车租借类还没有属性,它会变成系统的一个对外的接口。而汽车数据库需要哪些属性,则还根本没有决定。今后的阶段会解决这些问题。


用例分析第八步:验证分析机制

分析机制指的是高级的系统构建组件,它可以提供解决特定领域问题所需要的一些服务,而不是技术方面。例如,在保险领域,保单中的信息、声明和其它内容,在整个保险管理期间都是需要的。这个需求用分析机制来说,就叫做:持久化:无论程序是否运行,都一直维护数据的信息和状态。请注意我们并没有指定使用Oracle SQL,或是SQL Server这些特定的实现环境,我们只是列出了持久性,和我们后面会谈到的设计机制实现机制。实现机制将会是与特定平台或者软件供应商相关的。

我们在表4中试着举例说明,分析、设计和实现机制之间的关系:

表4: 说明,分析、设计和实现机制之间的关系

一些通用的分析机制是:

  • 持久性

  • 通讯(进程之间,或是应用程序之间)

  • 意外处理

  • 事件通知机制

  • 消息

  • 安全

  • 发布(也就是说,被发布的对象)

  • 遗留的系统接口

在汽车租借系统中,我们需要为这些指定分析机制:


结论

在“从用例到代码”的第一部分中,我们从一个用例开始,迄今为止已经找出了用来实现用例的类,它们之间的关系,和它们需要的属性。我们还找出了一些分析机制,在今后的设计和实现中会用到它。

如果我们对另一个用例再一次重复这整个过程,我们会发现另一些分析类,定义它们的职责,它们之间的关系。也许还会发现一些新的分析机制,画出新的协作图或是顺序图,来说明这些类如何交互。这演示了RUP过程的递增的特点:每个任务,每次迭代,都是在前面工作的基础之上进行的。

我们已经完成了很多工作,但是我们还无法开始写代码。下面我们的重点将会放到用例设计上面来,这就是本文第二部分的主题。

你可能感兴趣的:(用例分析)