资源描述
第4章 餐馆系统:业务建模
接下来的四章将考虑一个简单的案例,并给出一个从需求获取到实现的完整开发过程。我们将考虑一次单独的迭代,它通过统一过程标识的主要工作流之中的四个:即需求、分析、设计和实现,用例子说明UML表示法在软件开发中的使用。
由于本案例研究的意图在于强调开发的产品而不是过程,所以不会详细考虑由统一过程定义的这些工作流的结构,而在真正需要的地方将在介绍UML表示法的同时,简略介绍开发中涉及的活动。
4.1 非正式的需求
要开发的系统的意图是,通过改进为顾客预定和分配餐台的过程,支持一家餐馆的日常经营。这家餐馆当前采用一个手工预约系统,使用的是保存在一个大文件夹中的手写预约单。
图4.1是当前的预约单的一个例子,预约单中的每一行对应餐馆中一张特定的餐台。预约是对特定的一个餐台登记的,每个预约中记录有“餐具” 的数目,或者预期进餐者的数目,这样就能够分配一个大小适当的餐台。这家餐馆在晚间供应三次餐点,称为“简餐”、“正餐”和“夜点”时段。但如同预约单所表明的,这些时段无须严格遵守,可以预约跨多个时段的时间。最后,每个预约中要记录联系人的姓名和电话。
图4.1 手工预约单
为了记录各种事情,要在预约单上加一个注文。当一行用餐者到来并在他们的餐台就座时,就划掉相应的预约登记。如果他们就座的不是他们预约的餐台,就画一个箭头从最初预约的餐台指向新的餐台。如果顾客打电话取消预约,并不能从表中真正地擦除,而是做一个预约已经取消的注文。其他的信息,比如到什么时间餐台必须空出来,也可以写在预约单上。
如果有空闲的餐台,用餐者当然也可以不提前预约就进餐馆用餐,这被称为“未预约的顾客(walk-in)”,并在预约单中作为预约登记以表示餐台的占用,但不记录顾客的姓名或电话。
4.1.1 对计算机化系统的需要
这家餐馆的管理人员已经确认了很多与手工系统相关的问题。手工系统速度慢,而且,预约登记单很快就变得难以理解。这可能导致经营上的问题,例如,实际上有空餐台而由于这个预约单不是很明显,会妨碍顾客进行预约。没有备份系统:如果一张预约单被毁坏了,餐馆就没有了那个晚上有什么预约的记录。最后,从现有的预约单获取即使很简单的管理数据也很费时,例如餐台的使用率。
由于这些以及其他原因,该餐馆意欲开发一个现行的预约单的自动化版本。新系统应该和现有的预约单显示同样的信息,并且有大致相同的格式,使餐馆员工易于转换到新系统。当记录了新的预约时,或者对已有的预约进行修改时,应该立即更新显示,使餐馆员工在工作时总能使用可获得的最新信息。
系统必须易于记录餐馆营业时发生的有意义的事情,例如顾客的到来。系统的操作应当尽可能是直接操作屏幕上显示的数据。例如,可以简单地将预约拖动到屏幕上一个适当的位置以改变一个预约的时间或者分配的餐台。
4.1.2 定义一次迭代
迭代和增量的方法建议,一个系统的第一次迭代应该只交付足够使系统提供某些确实有商业价值的核心功能。在餐馆预约系统这个例子中,基本需求是餐馆在营业时记录预约和更新预约单信息的能力。如果这些功能可以使用,则可能用这个系统代替现有的预约单,然后在随后的迭代中再开发其他功能。
4.2 用例建模
在一个系统可能采用的不同视图中,用例视图(use case view)被认为是UML中起着支配作用的视图。用例视图描述的是系统外部可见的行为。因此,在软件开发开始于考虑所提出的系统的需求的情况下,用例视图确立了一种强制力量,驱动和约束着后续的开发。
用例视图展示的是系统功能的结构化视图。这个视图定义了若干参与者(actors)和这些参与者可以参与的用例(use case)。这些参与者模型化了用户与系统进行交互时可能充当的角色,一个用例则描述了用户使用系统能够完成的一项特定的任务。用例视图应该包含一组定义了该系统的完整功能的用例,或者至少定义了当前迭代所规定的功能的用例,这些用例应该以在系统支持下能够完成的任务的措词给出。
理想地,用例视图应该是客户、最终用户、领域专家、测试人员和任何其他的涉及系统的人员,不需要详细了解系统结构和实现就容易理解的。用例视图不描述软件系统的组织或结构,它的作用是给设计者施加约束,设计者必须设计出一个能够提供用例视图中指定的功能的结构。
4.2.1 用例
一组用例是一个系统的用户能够使用系统完成的不同任务。在这个例子中,我们将简单描述预约系统可能有的一组用例,但是在真正的开发中,用例一般是由分析人员与系统未来的用户进行磋商确定的。
餐馆预约系统第一次迭代的意图是允许用户使用一个自动化的预约单。可以通过考虑在系统实现后餐馆员工能够用它来做什么,简单地草拟出这次迭代的一组初步的用例。下面列出了这些用例所支持的主要任务:
1.记录一个新的预约信息(“记录预约”)。
2.取消一个预约(“取消预约”)。
3.记录一位顾客的到来(“记录到达”)。
4.将一位顾客从一张餐台移到另一张餐台(“调换餐台”)。
一个用例不单是简单地描述了该系统的部分功能,所以有时可以这样描述:一个用例描述了系统能够为一个特定的用户做些什么:一个用例描述的是一个自包含的任务,用户总是愿意把该任务作为他们正常工作的一部分。如果一组用例覆盖了用户使用系统完成的所有功能,这就为系统的功能需求已经完全指定提供了某种保证。
上面的列表简单确定和命名了一些候选的用例。为了更完全地理解每个用例中包含什么,必须如4.3节中所说明的那样,写出每个用例的详细描述。
4.2.2 参与者
一个用例描述了系统及其用户之间的一类交互。但是,系统通常有不同种类的用户,他们能够执行系统功能的不同子集。例如,多用户系统通常定义一个角色称为“系统管理员”:这个人有权访问普通用户不能使用的指定类别的功能,例如定义新用户,或者进行系统备份。
人与系统在进行交互时能够担任的不同角色称为参与者(actors)。参与者一般对应于对系统的一个特定的访问级别,由参与者能够执行的系统功能的类别定义。在其他情况下,参与者不是如此严格定义的,而是简单对应于一组对系统有不同兴趣的人。
在餐馆预约系统的案例中,所提出的用例可以分成两组。第一组由与维护提前预约信息有关的用例组成。顾客将联系餐馆提前预约或取消提前预约,一般地,接待员将接到这些电话并更新预约系统中存储的信息,因此,我们能够确定一个与相应用例关联的参与者。
在第二组中有许多任务需要在餐馆营业时执行,包括记录顾客的到来,以及为了适应不可预料的经营需要将一行用餐者从一个餐台移到另一个餐台。这些工作譬如说可能是一个侍者领班的责任,因此我们能够标识另一个与这些用例关联的参与者。
区分参与者很重要,参与者是用例模型的一个构成成分,并且是系统的真正用户。一般而言,参与者和用户之间不存在一对一的对应。例如,在一个小餐馆中,同一个人可以作为接待员和领班,可能通过使用具有不同访问特权的密码登录到系统。相反地,对应于一个参与者可能有许多真正的人:大餐馆在餐馆的每个房间或每层都可能有不同的领班。参与者甚至不需要是人类用户,例如,网络中的计算机可以直接互相通信,在某些系统中远程计算机可能最好能作为参与者建模。
4.2.3 用例图
用例图(use case diagram)以图解的形式概括了系统中的不同参与者和用例,并显示了哪些参与者能够参与哪些用例。餐馆预约系统的初始用例图如图4.2所示,其中包括了上面确定的参与者和用例。
图 4.2 初始用例图
用例图最简单的形式只是显示参与者、用例和它们之间的关系。参与者用一个像人一样的图标表示,用例用包含有用例名字的椭圆表示。在参与者参与的或者能够执行一个特定用例的地方,用一个连接参与者和相关用例的关联表示这种关系。
UML允许在用例图中包含更多的结构,来定义用例之间以及参与者之间的各种关系。例如,可以定义多个用例共有的一个共享行为,形式化为它在其他用例中的包含。
然而,在实践中不值得花费很多时间细化用例图,因为额外的关系对后面的开发起不到很大作用。如下一节所讨论的,更重要得多的是考虑每个用例指定的行为的细节。其他的用例图表示法将在适当的地方介绍。
4.3 描述用例
用例描述了系统和它的用户之间在一定层次上的完整的交互。例如,一个打电话给餐馆进行预约的顾客,会和餐馆的一位将在系统中记录预约的店员讲话。为此,该店员需要充当一个接待员,即使这并不是他们正式职位的描述,并且以某种方式和系统交互。在这种情况下,该店员被认为是接待员参与者的一个实例,发生在接待员和系统之间的交互是用例的一个实例。
在用例的不同实例中会发生什么的细节,会在很多方面有所不同。例如接待员必须要为每个新的预约输入特定的数据,如不同顾客的姓名和电话号码,这些数据在各个实例中都不尽相同。
更值得注意的是,一个用例实例中可能会出现差错,这样将不能达到原来的目的。例如,在用户要求的时间没有合适的餐台,用例的实例可能实际上将不会导致进行一个新的预约。一个用例的完整描述必须指明,在用例所有可能的实例中可能发生什么。
用例描述可能因此而包含大量信息,这就需要某种系统的方法来记录这些信息。但是,UML没有定义一种描述用例的标准方式。这样做的部分原因是,用例的意图是不拘形式地用作与系统未来用户进行沟通的一种辅助工具,所以重要的是开发人员应当有自由,用看来对用户有帮助并且容易理解的各种途径与用户讨论用例。尽管如此,在定义一个用例时能有一些可以考虑的结构还是有用的,为此,许多作者定义了用例描述的模板(template)。一个模板实质上是一个标题列表,每个标题概括了可能记录的一个用例的一些信息。在本章中,将简略讨论用例描述最重要的方面,但在附录C中给出了一个完整的模板。
4.3.1 事件路径
用例描述必须定义在执行用例时用户和系统之间可能的交互。这些交互可以作为一种对话描绘,其中用户对系统执行一些行为,系统于是以某种方式响应。这样的对话一直进行到该用例实例结束。
交互可以区分为“正常”交互和其他各种情况的交互。在正常交互中,用例的主要目标可以没有任何问题并且不中断地达到,而在其他情况中一些可选的功能会被调用,或者由于出错以致不能完成正常的交互。正常情况被称为基本事件路径(basic course of events),其他情况称为可选的(alternative)或例外的(exceptional)事件路径,取决于它们被看作是可选的还是错误。一个用例描述的主要部分是对用例所指定的各种事件路径的说明。.
例如,在“记录预约”用例中,基本事件路径将描述这样的情况:一位顾客打电话进行预约,在要求的日期和时间有一张合适的餐台是空闲的,接待员输入顾客的姓名和电话号码并记录预约。这样的事件路径,如下所示,能够以稍微结构化的方式表示,以强调用户的动作和系统响应之间的交互:
记录预约:基本事件路径
1. 接待员输入要预约的日期;
2. 系统显示该日的预约;
3. 有一张合适的餐台可以使用;接待员输入顾客的姓名和电话号码、预约的时间、用餐人数和餐台号;
4. 系统记录并显示新的预约。
在一个事件路径中,常常会想到包含类似“接待员询问顾客将要来多少人”这样的交互。其实这是背景信息,而不是用例的基本部分。事件路径要记录的重要事情是用户输入到系统的信息,而不是该信息是如何获得的。而且,包含背景的交互会使用例不如它在不包含时该有的可复用性,而且使得系统的描述比本来需要的更复杂。
例如,假定在餐馆关门时顾客在答录电话上留下了预约请求,这将由接待员在每天开始营业时处理。上面给出的基本事件路径,对接待员直接同顾客讲话或者从录音信息中取得的详细信息,同样适用:单一的用例“记录预约”将这两种情况都包括在内了。然而,如果用例描述包含对接待员和顾客的对话的引用,在处理一条录音信息时它将不能适用,就需要一个不同的用例。
如果在顾客要求的日期和时间没有可用的餐台,上面描述的基本事件路径就不能完成。在这种情况下会发生什么可以通过一个可选事件路径描述,如下所示:
记录预约——没有可用的餐台:可选事件路径
1. 接待员输入要求预约的日期;
2. 系统显示该日的预约;
3. 没有合适的餐台可以使用,用例终止。
这看起来有些简单,但是至少告诉我们,在这一点必须可能中断基本事件路径。在后续的迭代中,将可能为这种情况定义另外的功能,例如,可能将顾客的请求输入到一个等待名单中。注意,确定是否能够进行预约是接待员的职责,系统所能做的只是在输入预约数据后核对餐台实际是可用的。
可选事件路径描述的情况,可以作为营业的一个正常部分出现,它们并没有指出产生了误解,或者发生了错误。在另外一些情况下,也许因为一个错误或用户的疏忽而不可能完成基本事件路径,这些情况则由例外事件路径描述。
例如,我们能够预料在餐馆客满时会有许多顾客要求预约,接待员没有任何办法解决这个问题,所以要通过一个可选事件路径来描述。相反地,如果接待员错误地试图将一个预约分配到过小的不够所要求的就餐者人数的餐台就座时,这可能就要作为一个例外事件路径描述了。
记录预约——餐台过小:例外事件路径
1. 接待员输入要求预约的日期;
2. 系统显示该日的预约;
3. 接待员输入顾客的姓名和电话,预约的时间,用餐人数和餐台号;
4. 输入的预约用餐人数多于要求餐台的最大指定大小,于是系统发出一个警告讯息询问用户是否想要继续预约。
5. 如果回答“否”,用例将不进行预约而终止;
6. 如果回答“是”,预约将被输入,并附有一个警告标志。
不同类型的事件路径之间的区分是非正式的,它可以使用例的总体描述组织得更容易理解。以同样的方式描述所有的事件路径,在后续的开发活动中就可以用类似的方式处理。因此,譬如说在不明确的情况中,不值得花费过多的时间去决定一个特定的情况是可选的还是例外,更重要的是一定要确认给出了必需行为的详细描述。
4.3.2 用户界面原型
尽管上面给出的事件路径描述了用户和系统之间的交互,但是,它们没有明确地详述这些交互是如何发生的。例如,虽然说明了接待员输入一个新预约的各种信息,但是没有说明是如何做的,是直接键入到预约单中,还是在对话框中填写,或者完全是通过其他的方法。
一般而言,在用例描述中详述用户界面不是个好主意。用例描述的重点是定义系统和用户之间交互的总体结构,而包含用户界面的细节会使之不清晰。并且,用户界面应该被设计得协调一致并便于使用,而这只有合理地考虑了各式各样的用户任务才能做到。如果用例描述不适当地指定了用户界面的细节,可能会使用户界面设计者的工作更加困难,或者需要大量改写用例描述。
不过,对用户界面像什么样子有一个大概的看法,可能会有助于理解用例描述。在餐馆预约系统的例子中,我们知道系统是为代替现有的预约单而设计的,那么很可能屏幕设计将接近当前使用的预约单的结构,所以我们假定屏幕布局将类似于图4.3中所示。
图4.3 预约系统主屏幕的一个原型
屏幕的主体显示已有的预约。在屏幕左边列出的是餐台,屏幕上部是时间。一个预约由一个显示着相关数据的淡阴影的矩形表示。预约的日期显示在屏幕的顶部。
这个草图没有指定数据是如何由用户输入的。对于“记录预约”用例,一个可能是用户首先在日期框内键入需要的日期,得到一个顾客要求的那天已有预约的显示,然后也许可以从一个菜单中选择“记录预约”选项,并将预约数据输入到一个对话框。当完成后,应立即更新显示器,显示新的预约。
4.4 组织用例模型
一旦已经记录了一个预约,接下来必须要处理的重要事件是顾客到达餐馆,这由我们称为“记录到达”的用例描述。该用例的基本事件路径如下:
记录到达:基本事件路径
1. 侍者领班输入当前日期;
2. 系统显示当天的预约;
3. 侍者领班确认一个选定的预约已经到达。
4. 系统对此进行记录并更新显示器,将顾客标记为已到达。
在这个用例中,如果系统记录中没有到达顾客的预约,可能发生一个可选事件路径。在这种情况下,如果有适当的餐台是空闲的,则创建一个未经预约的登记。
记录到达——没有提前预定:可选事件路径
1. 侍者领班输入当前日期;
2. 系统显示当天的预约;
3. 系统中没有记录该顾客的预约,所以侍者领班输入预约时间、用餐人数和餐台号,创建一个未预约登记,
4. 系统记录并显示新预约。
比较这些事件路径和为“记录预约”用例所写的事件路径,显示出在这两个用例中存在着相当数量的某些共享功能。与其多次写出相同的交互,一种更好的方法是在一个地方定义共享行为并在适当的地方引用它。UML定义的用例图表示法提供了一些可以这样做的方法,能够产生一个更简单和结构更好的用例模型。
4.4.1 用例包含
迄今描述的事件路径中,也许其中最明显的冗余是,它们全都是从参与者输入一个日期,然后系统响应,显示该日记录的预约而开始的。如果这个公共的功能能够以某种方法反映在模型的结构中将是有益的,这样,就没有必要重复写出这个公共功能。
实际上,这个交互非常可能会形成一个完整的用例。例如,餐馆经理可能试图计算一个特定的晚上要雇佣多少个侍者,那么,简单地看看当天的预约可能是估计餐馆大约会有多繁忙的一个好办法。但是,当前规定的这个模型却做不到这一点,因为检查给定日期的预约只能作为其他用例的一部分来执行。
这个理由提示我们,应该定义一个相应于显示给定一天的预约的任务的新用例。这个用例能够被餐馆的任何工作人员执行,因而任何参与者都可以在下面对基本事件路径的描述中被提及。
显示预约:基本事件路径
1. 用户输入一个日期;
2. 系统显示当日的预约。
这个新用例和已经描述的用例之间的关系可以这样来描绘:只要在执行其他用例之一时就包含“显示预约”用例中的交互。
这种关系需要在用例描述和用例图中予以清晰化。在一个用例描述中,如下面版本的“记录预约”用例的基本事件路径描述的,包含其他的用例可以非形式地说明。
记录预约:基本事件路径(修改)
1. 接待员执行“显示预约”用例;
2. 接待员输入顾客姓名和电话号码、预定的时间、用餐人数以及预留的餐台;
3. 系统记录和显示新预约。
一个用例和它所包含的其他用例之间的关系在用例图中用一个连接两个用例的虚线箭头表示,称为依赖性(dependency), 用一个指定所描述关系的类型的构造型(stereotype)标记。图4.4表示了“记录预约”和“显示预约”之间的“包含(include)”依赖性。
图4.4 用例包含
注意,除了包含依赖性,图4.4还含有另外一个关联将该参与者连接到“显示预约”用例。这指明了该用例可以由接待员独立于进行新的预约来操作。
4.4.2 参与者泛化
图4.2中的用例图可以很容易地被更新为包含新的“显示预约”用例。因为接待员和侍者领班都能够执行新用例,图中将包含从每个参与者到这个新用例的一个关联。
但是,这些关联表示相同的关系,因为我们假定任何人都能够显示给定日期的预约。与其两次显示实质上相同的关系,我们可以通过定义一个新的参与者表示接待员和侍者领班共有的东西来简化该图。图4.5中描述了这个新参与者,它表示餐馆所有员工可以共享的能力,因而称为“员工(staff)”。已有的参与者通过泛化(generalization)与新参与者相关,表示它们被看作是“员工”的特殊情况,定义了只能由一个员工子集共享的附加的特性。
图4.5 参与者泛化
参与者之间泛化的含意是,特化的参与者可以参与和更一般的参与者关联的所有用例。因此,图4.5指定了接待员和侍者领班都可以执行“显示预约”用例。另外,可以指派给特化的参与者更多的责任,图4.5指定只有接待员才能够记录预约,而只有侍者领班才可以记录到达,这和图4.2中最初的模型中定义的一样。
4.4.3 用例扩展
“记录到达”用例的可选事件路径规定,如果系统没有记录一个顾客的预约,侍者领班将通过创建一个未预约登记来表示他们在餐馆用餐的事实。但是,将记录未预约登记表示为单独一个用例可能更好一些,因为未预约登记将会为那些从不提前进行预约的顾客创建,而且该用例可能需要独立于“记录到达”用例执行。
“记录未预约顾客”用例的基本事件路径将会被某个没有预约就来用餐人触发。它的结构非常类似于“记录预约”用例,只是在记录的细节上不同。基本事件路径可以如下描述:
记录未预约顾客:基本事件路径
1. 侍者领班执行“显示预约”用例;
2. 侍者领班输入时间、用餐人数和分配给顾客的餐台;
3. 系统记录并显示新预约。
但是,现在看起来在“记录到达”用例的可选事件路径和这个新用例的描述之间有相当多的重叠。很自然地会问到,能否通过将两个用例以某种方式相关而消除重叠,或许通过上面介绍的包含依赖性。
包含依赖性对这种情况并不适合,因为在“记录未预约顾客”中指定的交互不是在每次执行“记录到达”时都执行。
更合适的是,它们之间有一种可选的关系:“记录未预约顾客”用例只是在“记录到达”的某些情况下被执行,也就是对该顾客没有记录的预约、有一个适当的餐台空闲着、并且顾客还想在餐馆用餐时才被执行。
UML通过假定在某种情况下,“记录到达”用例可以被“记录未预约顾客”用例扩展,来描述这种情形。这在用例模型中可以通过一个标记为‘extends’的构造型的依赖性表示,如图4.6。注意,这种依赖性和包含依赖性还有其他不同,即箭头是从扩展的用例指向被扩展用例。
图4.6用例扩展
包含依赖性和扩展依赖性之间的区别相当微妙,并且对于这两种构造型的确切含意也有许多争论。但是,用例模型比其他的UML模型使用起来更不正式,所以通常不值得花过多的时间被用例之间这些关系的细节所困扰。建立用例模型的目的是使系统的需求更容易理解,这个目的可以通过写用例描述更好地达到,用例描述可以更清楚地说明每个用例中发生了什么。
4.5 完成用例模型
图4.2中剩下的两个用例很容易处理。例如,取消预约的基本事件路径可以如下指定:
取消预约:基本事件路径
1. 接待员选择要求的预约;
2. 接待员取消该预约;
3. 系统询问接待员确认取消;
4. 接待员回答“是”,系统记录取消并更新显示。
这个事件路径没有清楚地详细说明用户将如何完成这些任务。如上所述,用户界面的细节最好在后面对每个用例的功能都得到了很好的理解时再指定。
不指定用户界面细节的另外的好处是,这为系统能提供多种方式完成该任务留下了不受限制的可能性。例如,取消一个预约的一种方法可能是通过菜单选择,引出一个对话框,输入该预定的标识细节以进行选择。另一种方法是在一个预约矩形框上单击鼠标右键可能弹出一个菜单,包含一个“取消”选项。这些都是达到同样目标的方法,即取消预约,在这个阶段以一种足够通用的包含这二者的方式写出用例描述是较好的想法。
要完成这个用例描述还有许多可选和例外事件路径要考虑。例如,可能餐馆的经营规定禁止取消过去某段时间的预约或已经记录了顾客到来的预约。对这些可选情况的规格说明留作习题。
“调换餐台”用例的基本事件路径也可以独立于用户界面的细节进行定义如下:
调换餐台:基本事件路径
1. 侍者领班选择需要的预约;
2. 侍者领班改变该预约的餐台分配;
3. 系统记录改变并更新显示。
这个用例可以通过一个菜单选项调用,由用户在一个对话框中填写新的餐台号,或者通过将预约矩形拖到它的新位置完成调换餐台。可选和例外事件路径可以从餐馆的经营规则得到:和取消一样,应该不可能将一个过期的预约调换到新餐台,也应该不可能将一个预约移到已占用的餐台。
什么时候一个用例模型完成?
最后的这两个用例的考虑可能使人联想到,使用在4.4节中介绍的组织机制对用例模型更进一步加细。例如,取消和调换餐台两个用例都涉及选择一个预约并更新系统保存的关于它的一些信息。或许应该确定一个独立的选择预约的用例,包含在这两个用例之中。也许应该确定一个更通用的用例,可能叫“更新预约”,为用户提供一种一般的机制,修改与一个预约相关的数据,例如用餐人数,或者结束时间。
然而,用例分析是一项非正式的技术,在一定时间之后再花时间寻求对模型的改进时会降低回报。这对包含关系和扩展关系尤其适用:这些关系通常与从用例产生的设计的结构特性并不相当,所以缺少一个可能的依赖性的后果并不严重。
图4.7描述的是一个完整的用例图,它是总结了上面对餐馆预约系统的第一次迭代中的用例讨论的结果。这将作为后续章节中对这个案例进一步开发的基础。
图4.7 完成的用例图
4.6 领域建模
用例的意图是使系统的开发人员和用户都容易理解,因此要用来自业务领域的术语进行描述,而不是使用面向实现或计算机的词汇。通常和用例建模同时进行的一个有用活动是系统地文档化在用例描述中使用的业务概念。
文档化业务概念这个活动的一种常见方法是产生一个类图,以显示最重要的业务概念和它们之间的关系。这样的类图通常称为领域模型(domain model),对大规模的项目,领域模型通常作为一个更艰巨的业务建模活动的一部分产生,但是较小的系统通常可以用一个简单的领域模型充分地描述。
领域模型一般不会用到全部类图表示法。在领域模型中,通常,类表示在系统的现实世界环境中具有意义的实体或概念。系统必须记录的数据作为这些类的属性建模,而且一个领域模型还用关联和泛化显示了这些概念之间的关系。领域模型通常不包含操作,这将在后面更详细地考虑用例的实现时定义。
在餐馆预约系统中,关键的业务需求是记录顾客已经预定的事实,所以领域建模可以从标识表示预定(reservation)和顾客(customer)这两个类开始。我们知道,系统必须记录每个进行预定的顾客的姓名和电话号码,所以比较合理的是将这些作为顾客类的属性建模。
顾客已经进行了预定的事实可以通过将该顾客链接到该预定来记录,因此在领域模型中,这两个类之间的关联就建立了顾客进行预定这个事实的模型。应该规定这个关联的重数,每个预定是由一个顾客进行的,这个人的姓名和电话由系统记录,但是每个顾客可以进行多个预定。这些建模结果的记录如图4.8所示。
图4.8 顾客和预定建模
然而,在预定和顾客之间还有更进一步的关系应该包含在领域模型中。这是由一个事实引起的,即预约通常是为多于一个人进行的,所有用餐的人都可以被描述为餐馆的顾客。
在领域模型包含这个关系之前,我们应该先考虑系统必须维护的信息。进行预定的人的姓名和电话必须记录,这样,如果有什么问题时可以和他们联系。但是对于到来用餐的人,重要的只是他们有多少人,餐馆记录每个用餐者的个人详细信息并不必要。因此,可能并不需要将一个预定链接到所有会来用餐的顾客,而是应该将用餐人数作为一个属性包含在预定类中。
接下来我们可以考虑对于预定必须记录的信息。预定的日期和时间是很直接的属性,可以作为属性建模。系统还必须记录分配给一个预定的餐台,这可以通过将餐台号作为预定的另一个属性来记录,但是,还有一个选择是将每张餐台作为一个自主对象建模,因而在领域模型中引入了一个餐台类。
有时很难决定是应该将一个特殊的信息作为一个类还是作为一个属性包含在领域模型中。对餐台这种情况,一个有关的考虑是,餐馆需要记录每张餐台的其他信息,例如,餐台可以容纳的用餐人数。在一个对象模型中,这个信息必须作为一个类的属性记录,而餐台类是存储该信息的很自然的地方。图4.9是扩充以后包含了预定的各种特性的领域模型。
图4.9 包含预定的特性的领域模型
预定类和餐台类之间的关联的重数指明一个预定只能对应一张餐台。这似乎排除了餐馆为就座于多个餐台的大团体提供预定的可能。我们假定在开发的第一次迭代中这个需求可以充分地处理,方法是对这样的团体进行多个、同时的预约,每个预约对应一张要占用的餐台。
在关联的另一个方向,这个重数断言对每张餐台可以进行多个预定,这并不是意味着隐含会同时对一个餐台有两个预定的意思,而是指在不同的日期和不同的时间,餐台可以被分配给不同的顾客。
但是,餐台不能重复预定,显然是一个重要的经营规定。这一点不能通过UML提供的图形化表示法表示,这样的特性是通过给相关的模型元素增加约束(constraints)建模的。约束是一个系统的所有状态都必须满足的特性,将在第12章详细讨论。图4.9是在关联上显示了一个非形式化的约束,排除了重复预定餐台的可能性。
对这个关联的另外一个似乎合理的约束是表明一个预定的用餐人数不能大于该预定所链接的餐台的座位数。在大多数情况下,将会遵守这个约束,但在“记录预约”用例的描述中清楚地表明,在例外情况下,为了使大团体在那里就座,一个餐台可以有额外的位置。因此,要增加将这种可能性排除在外的约束,将会和用例模型不一致。
领域模型没有涉及未预约的就餐者(walk-in),未预约和预定有一些共同的属性,就是存储的基本数据和到餐台的链接,但不同的是对未预约的顾客没有顾客信息的记录。这表示预定和未预约应该通过定义一个一般类建模,该类记录二者的共同特性,而用特化的子类建立预定和未预约顾客的模型。对模型的这个细化如图4.10所示。
图4.10 包含未预约的领域模型
在图4.10中WalkIn类看起来是多余的,因为它没有对从Booking类继承的特性进行任何增加。然而,将它作为一个包含在模型中的单独的类,对未来的改变起到了一种保险作用。如果后面需要在模型中增加未预约用餐者的一个特性,而预约没有该特性,在图4.10的设计中很容易做到。但是,如果简单地将WalkIn作为其父类的实例创建,那么涉及到的模型的修改会相当多。
领域模型的正确性
在开发一个领域模型时,提出对系统某些方面建模的另一种方式很常见,而且通常看起来也没有明显的理由去选择模型的一个版本,而不是选择另一个。这种例子包括图4.10中对餐台号和未预约用餐者预约的处理,另外,可能会建议日期应该作为一个单独的类建模,而不是简单地作为预约的一个属性。
某些模型明显是错误的,或者是不充分的。如果根本没有包含日期,模型将不能储存预约系统需要的一项关键数据。但是,要证明一个模型的正确性或者即使是一个模型优于另一个模型,要更困难一些。从领域建模的角度看来,在将日期作为属性建模或者作为一个链接到相关预定的单独类的实例之间作出选择似乎没有太多的困难。
这个问题可以放到一个完整的开发中,从领域模型的目的的角度来考虑。因为一个设计人员最终是要确定一组对象,它们能够以有效地支持整个系统必须交付的功能的方式进行交互。因此,领域模型中可供选择的方式,从做到这一点的程度上,可以最好地评价。
然而,这个问题并不能通过孤立地检查领域模型而简单地评定。还必须通过观察领域模型中的类的实例之间的交互实际上是如何支持需要的功能,考虑模型实际上表达了什么。这是统一过程中分析和设计工作流的关键活动,将在接下来的两章中考虑。
4.7 术语表
领域模型的一个有用的结果是对客户用以谈论系统的概念和词汇的一个详细的考虑。在非正式地描写一个系统时很容易使用不明确或不一致的术语。例如,术语“预约(booking)”和“预定(reservation)”的交替使用遍及本章,但是,图4.10表明实际上在预约的一般概念和一个提前进行的预定之间是有区别的,因而建议对这两个术语更仔细的定义。
通常,将一个系统的核心词汇以一系列定义收集到一个系统术语表(glossary)中进行总结很有用。例如,在预约系统的开发中,迄今为止使用的术语可以列出来,并定义如下:
预约(Booking):分配一张餐台给一行用餐者进餐。
用餐人数(Covers):预定将来用餐的人数。
顾客(Customer):进行预定的人。
用餐者(Diner):在餐馆用餐的人。
位子(Places): 在一张特定餐台能够就座的用餐者人数。
预定(Reservation):提前预约一个特定时间的餐台。
未预约(Walk-in):没有提前进行的预约。
理想地,一旦创建了一个术语表,所有的系统文档在编辑时应该一致地使用已定义的术语。在后续的章节中,餐馆预约系统的进一步开发将使用上面定义的术语,但编辑本章前面给出的用例描述以反映这些正式定义将留作习题。
4.8 小结
·在业务建模活动结束时,系统文档包含一个用例模型、对各个用例的文字描述、一个关键术语的术语表以及一个领域模型。
·用例图描述了参与者和用例以及它们之间的各种关系。
·用例表示了一类用户可以利用系统完成的典型任务。
·参与者表示了用户在与系统交互时可以充当的角色。参与者和用例的关联,表示以给定角色工作的用户能够执行哪些任务。参与者可以通过泛化相关,以明确地表示它们共享的能力。
·一个用例可以包含另一个用例:意思是被包含用例规定的交互构成包含用例每次执行的一部分。
·一个用例可以扩展另一个用例:意思是扩展用例规定的交互构成被扩展用例的一次执行的一个可选部分。
·用例描述是文字形式的文档,详细描述在执行用例时在用户和系统之间可以发生的交互。
·每个用例描述包含一个基本事件路径,描述用例的“正常”进行,以及一组可选和例外事件路径。
·领域模型显示重要的业务概念、它们之间的关系和由系统维护的业务数据。领域模型表示为类图,一般只显示类、属性、关联以及泛化。
·术语表定义业务领域中的重要术语,为每个术语提供一个一致同意的定义,定义了应该在用例描述中使用的特定业务的词汇。
4.9 习题
4.1 假定餐馆经理看了图4.2后批评说设计不完善,因为餐馆雇不起一个专职的接待员。你将如何回答?
4.2 重画图4.7中的用例图,没有“员工”参与者,而且不使用参与者泛化。从餐馆经营的角度来看,这两个版本的用例图描述的是相同的事实吗?你认为哪个图更清楚并且更容易理解?
4.3 扩充“记录预约”用例的描述,使其包含接待员试图重复预定某个餐台的情况。这是一个可选的还是一个例外的事件路径?
4.4 系统应该不允许侍者领班对一个预定多次记录到达。考虑系统可能阻止其发生的方法,如果必要,用“记录到达”用例的一个新的事件路径描述系统的反应。
4.5 写出你能想到的“显示预约”用例的任何可选或例外事件路径的描述。
4.6 扩充“取消预约”和“调换餐台”用例的描述,使之包含一个完整的可选和例外事件路径的列表。
4.7 在用例描述中,对可选和例外事件路径能否进行清楚和无歧义的区分?用来自餐馆预约系统的例子给出你的答案的理由。
4.8 用附录C中讨论的模板和4.7节中的术语表中列出的术语重写本章给出的非正式的用例描述。
4.9 本章的讨论对如何修改预约信息很少谈及。预约只能被取消,或者移动到另一个餐台。扩充用例模型,以允许对预约进行更一般的编辑,譬如说是通过使用对话框显示一个预约的全部信息并允许进行适当修改。
4.10 按照现在的情况,领域模型将允许任意数目的一行人被分配到任意餐台。假定餐馆决定正式规定能够加到一个餐台的额外位子的数目为“满座数”。和以前一样,应该询问用户确认对该餐台的预约人数过多,但决不能超过餐台的满座数。更新进行预定的用例描述以适应这个新需求,并适当地修改领域模型。
4.11 假定餐馆决定提供指定的可抽烟餐台和无烟餐台。需求的这个改变对本章中提出的用例模型会有什么影响?
4.12 本章中的讨论没有提到预约的时间长短:只是输入了到达时间,并且我们隐含地假定所有预约有一个标准的时间长度。扩充用例模型使之允许在预约的时间长短上有更多的灵活性,用定义一个新用例的方法调整预约的长短。这可以通过明确地输入时间完成,或者通过使用鼠标改变显示的预约矩形的长度完成。
4.13 在第一次迭代中,预定由接待员手工分配到餐台。假定已知预约的日期和时间以及用餐者人数的信息,为系统自动分配预约到餐台的一个增量写出用例描述。
4.14 扩充预约系统以支持等待名单。不能在要求的时间进行预定的顾客应该有放入等待名单的机会。当有餐台可用时,可能是因为取消预约,系统
展开阅读全文