目标:1》定义系统操作2》为系统操作创建契约
简介 在UP中,用例和系统特性是用来描述系统行为的主要方式,并且足以满足要求。有时需要对系统行为进行更为详细和精确的描述。操作契约使用前置和后置条件的形式,描述领域模型里对象的详细变化,并作为系统操作的结果。领域模型是最常用的OOA模型,但是操作契约和状态模型也能够作为有用的与OOA相关的制品。 操作契约可以视为UP用例模型的一部分,因为它对用例指出的系统操作的效用提供了更详细的分析。图11-1所示的UP制品的相互影响强调了操作契约。该契约的主要输入时SSD中确定的系统操作、领域模型和领域专家的见解。该契约也可以作为对象设计的输入,因为他们描述的变化很可能是软件对象或数据库所需要的。
示例 下面给出的是系统操作enterItem的操作契约。其中的关键元素是后置条件,其他部分虽然有用但重要性稍低。
契约CO2:enterItem
操作:enterItem(itemID:ItemID,quantity:integer)
交叉引用:用例 : 处理销售
前置条件:正在进行的销售
后置条件:1》创建了SalesLineItem的实例sli(创建实例)。2》sli与当前Sale关联(形成关联)3》sli.quantity赋值为quantity(修改属性)4》基于itemID的匹配,将sli关联到ProductDescription(形成关联)
“(创建实例)”这样的分类是为了帮助学习,并不是契约的有效部分。
定义:契约有哪些部分 下面对契约中的每个部分进行了描述:
操作:操作的名称和参数
交叉引用:会发生此操作的用例
前置条件:执行操作之前,对系统或领域模型对象状态的重要假设。这些假设比较重要,应该告诉读者。
后者条件:最重要的部分。完成操作后,领域模型对象的状态。后续章节将详细描述这个问题。
定义:什么是系统操作 可以为系统操作定义操作契约,系统操作时作为黑盒构件的系统在其公共接口中提供的操作。系统操作可以在绘制SSD草图时确定,如图11-2。更精确地讲,SSD展示了系统事件,即涉及系统的事件或I/O消息。输入的系统事件意味着系统具有用来处理事件的系统操作,正如OO消息(一种事件或信号)要由OO方法(一种操作)来处理那样。 涉及所有用例的系统操作的完整集合将系统视为一个构件或类,定义了公共的系统接口。在UML中,作为整体的系统可以表示成名称为(例如)System的类的一个对象。
定义:后置条件 后置条件(postcondition)描述了领域模型内对象状态的变化。领域模型状态变化包括创建实例、形成或消除关联以及改变属性。 后置条件不是在操作过程中执行的活动,相反,它们是对领域模型对象的观察结果,当操作完成后,这些结果为真,就像浓烟散去后所能够清晰看到的事物。 概括说来,后置条件可以分为以下三种类型:(1》创建或删除实例 2》属性值的变化 3》形成或消除关联(精确地讲,是UML链接))。消除关联比较少见。删除实例的后置条件也十分少见,因为人们通常不会关心明确强制销毁现实世界中的事物。
后置条件如何与领域模型相关 这些后置条件主要是在领域模型对象的语境中表示的。可以创建什么实例?(来自于领域模型)可以形成什么关联?(来自于领域模型),等等。
动机:为什么需要后置条件 首先,后置条件并不总是必要的。在大多数情况下,系统操作的效果对开发者而言是相对清晰的,他们可以通过阅读用例、与专家交流或根据自己的知识对此进行理解。但有时需要更详细和精确的描述。契约正提供了此类描述。注意,后置条件支持细粒度的细节和精确性,以声明操作必须具备的结果。在用例中也可以表示为这种详细级别,但并不适宜,因为过于冗长和详细。契约是优秀的需求分析或OOA工具,能够详细描述系统操作(就领域模型对象而言)所需的变化,而不需描述这些操作是如何完成的。换言之,设计可以被延迟,我们可以重点分析必须发生的事物,而不是如何实现这些事物。
准则:如何编写后置条件 用过去时态表达后置条件,以强调它们是由操作引起的状态变化的观察结果,而不是发生的活动。这也是后置条件名称的由来!例如 (较好)创建了SalesLineItem 而不是 (较差)创建SalesLineItem或SalesLineItem被创建。
准则:后置条件因该完善到何种程度?敏捷与重量级分析 契约有可能是无用的。这一问题将在后续章节中讨论。但是假设契约有用,则为所有系统操作生成完整详细的后置条件集合是不可能的,或是没有必要的。就敏捷建模的本质而言,只是将其视为初始最佳的猜测,在这种理解下,详尽的后置条件是无法完成的,而所谓“完善”的规格说明也是几乎不可能的或不可信的。但是要理解,进行轻量的分析是现实和有效的,这并不是意味着要在编程前放弃任何调查,这又是另一种极端的误解。
示例:enterItem的后置条件 以下内容剖析了enterItem系统操作后置条件的动机。
创建和删除实例 输入itemID和商品项目的quantity后,会创建哪些新对象?SalesLineItem。因此:
- 创建了SalesLineItem的实例sli(创建实例)。
注意对实例的命名。该名字简化了在其他后置条件语句中对该新实例的引用。
属性修改 在收银员输入商品项目标识和对应的商品数量后,应该修改了哪些新对象或现有对象的属性?SalesLineItem的quantity应该变为等于quantity参数。因此
- sli.quantity赋值为quantity(修改属性)
准则:是否应该更新领域模型 通常在创建契约的过程中会发现,需要在领域模型中记录新的概念类、属性或关联。不要局限于先前定义的领域模型,当你在思考操作契约过程中有新发现时,要对领域模型进行改进。在迭代和进化式方法中(并且反映了软件项目的真是情况),所有分析和设计制品都不是完善的,要根据新发现对其改进。
准则:契约在何时有效 在UP中,用例是项目需求的主要知识库。用例可以为设计提供大部分或全部所需细节。这种情况下,契约就没有什么作用。但有时对于用例而言,所需状态变化的细节和复杂性难以处理或过于细节化。如果开发者在没有操作契约的情况下,能够准确地理解所需要完成的工作,则可以不编写契约。
准则:如何创建和编写契约 创建契约时可以应用以下指导:1》从SSD中确定系统操作。2》如果系统操作复杂,其结果可能不明显,或者在用例中不清楚,则可以为其构造契约。3》使用以下几种类别来描述后置条件:1》创建和删除实例。 2》修改属性 3》形成清除关联。
编写契约
- 如上所述,以说明性的、被动式的过去时态编写后置条件,以便强调变化的观察结果,而非其如何实现的设计。例如:(较好)创建了SalesLineItem。 (较差)创建SalesLineItem。
- 记住,要在已有或新创建的对象之间建立关联。例如,当enterItem操作发生时,仅创建SalesLineItem的实例是不够的。操作完成后,还应该将此信创建的实例与Sale关联。因此: 将SalesLineItem与Sale关联(形成关联)。
最常见的错误 最常见的问题是遗漏了关联的形成。特别是当创建了新实例时,通常需要建立与若干对象的关联。不要遗忘这一点!
应用UML:操作、契约和OCL UML正式地定义了操作(operation)。引证如下:操作是可以调用对象执行的转换或查询的规格说明。例如,在UML中,接口元素就是操作。操作是抽象而非实现。相比之下,方法是操作的实现。引证如下:操作的实现。它规定了与操作关联的算法或过程。在UML元模型中,操作具有特征标记,在这种语境中最为重要的是,与一组UML约束对象关联,这些对象被分类为指定操作语义的前置条件和后置条件。概括一下,UML通过约束定义操作的语义,这些约束可以用前置或后置的形式指定。要注意,本章强调UML操作的规格说明不能表示算法或解决方案,而只能是状态的变化或操作的效果。契约除了用于指定整个System(也就是说,系统操作)的公共操作外,还能够用于任何粒度的操作:子系统的公共操作(或接口)、构件、抽象类等。例如,可以为Stack这样的单个软件类定义操作。本章讨论的粗粒度操作属于将整个系统作为黑盒构件的System类,但是UML操作可以属于任何类或接口,它们都具有前置和后置条件。
用OCL表示的操作契约 本章中的前置后后置条件的格式是非正式的自然语言格式,完全可以被UML接受,并易于理解。但UML还具有正式、严谨的语言,成为对象约束语言(OCL),OCL可以用来表示UML操作的约束。准则:除非有不可避免的实际原因要求人们学习和使用OCL,否则要保持简单并使用自然语言。尽管我确信存在真实(且有效)的应用,但我从未见过使用OCL的项目,即便我调查了大量客户和项目。
过程:UP的操作契约 前置和后置条件契约式UML指定操作的常用风格。在UML中,操作在许多级别中存在,从System到细粒度的类。系统级别的操作契约式用例模型的一部分,尽管原始的RUP或UP文档中并没有正式地强调这一点。RUP作者证实了用例模型中包括操作契约。
阶段 初始--初始阶段不会引入契约,因为过于详细。 细化--如果使用契约的话,大部分契约将在细化阶段进行编写,这时已经编写了大部分用例。只对最复杂和微妙的系统操作编写契约。