面向对象分析与设计
上QQ阅读APP看书,第一时间看更新

2.7.4 OCL表达式

对象约束语言主要使用的方式是编写OCL表达式(OCL Expression),并使用它们描述UML模型元素所需要的规则和约束。

OCL表达式可以看成是一个由运算符和操作数组成的表达式,每个表达式均具有一个类型并返回一个结果。

为了明确OCL表达式与UML模型元素之间的关系,每个OCL表达式都必须具有明确的上下文(Context)。在OCL表达式中,上下文可以看成是对模型中某个特定的UML模型元素的一个引用,通过这个引用可以为模型元素定义期望的规则或约束。

上下文可以是UML模型中的任何类、属性、方法以及类之间的关联。上下文为表达式指明了表达式希望约束或描述的目标。没有明确上下文的表达式将毫无用处。

图2-28给出了一张类图,图中包含了两个类及其关联关系。后面介绍的部分例子中将会以这张图中的内容为上下文定义需要表示的业务规则。

图2-28 类图实例

在模型中使用OCL表达式时,可将表达式放置在相关上下文的附近位置或与上下文相关的注释中。下面将通过一些例子,说明OCL表达式的设计和使用方法。

OCL表达式通常可以分成不变量、操作约束、初始或派生规则以及导航等多种形式。

1.不变量(Invariant)

不变量表达式通常以模型中某个特定的类为上下文,描述这个类的所有实例应该满足的约束条件。

例如,对于图2-28中的Company类,定义了“员工数大于50”的约束,可以使用如下的表达式表示。

表达式第一行中context是一个OCL关键字,表示其后面的类名Company是整个表达式的上下文。其含义是表达式的作用范围是Company类的所有实例。符号inv是一个关键字,表示该表达式是一个不变量表达式。

第二行定义了一个用布尔表达式(numberOfEmployees >50)表示上下文需要满足的条件,其中number Of Employees被隐含地解释成Company类的属性。

两行语句组成了一个完整的OCL表达式,它表达的语义是:“Company类的number Of Employees属性值必须大于>50”或“公司员工数>50”。这个表达式为Company类定义了一个约束。

下面列出了三个使用了不同表示法的等价表达式:

其中的第一个表达式使用了self关键字,self表示上下文Company类的一个实例,其作用是明确了number Of Employees是Company类的属性。

第二个表达式使用Company类的实例变量c作为上下文,c被视为Company类的一个实例,表达式明确了number Of Employees是实例变量c的属性。

最后一个表达式将enough Employees定义为OCL表达式的名字,这使得可以在其他地方使用这个名字引用这个约束。

这三种形式的表达式的语义相同,但它们使表达式更明确也更无二义性。在不引起混淆的情况下,可以使用省略self、变量名和规则名字等这样的表示。

2.操作约束(Operation Constraint)

OCL表达式可以定义模型中与类操作有关的约束,操作约束可以分为操作的前置条件、后置条件和操作体约束。其中,前置条件和后置条件分别表示了操作执行前和执行后必须满足的条件。操作体约束表示操作本身在执行过程中应满足的条件。因此,操作约束实际上也是一种不变量约束。

前置条件通常是对其输入参数的约束。后置条件则主要是对操作输出结果的约束。定义前置条件或后置条件的方法就是使用pre和post关键字表示前置条件和后置条件。另外,关键字result表示操作的返回结果。定义操作约束的语法格式定义如下。

例如,为Person类的InCome操作定义前置和后置条件。

操作体约束使用body作为表达式条件的前缀。在一般情况下,表示一个查询操作的结果。表达式的类型必须与操作的结果类型相一致。与前置条件和后置条件一样,可以在表达式中使用操作参数。

前置条件、后置条件和操作体表达式可以是混合在一个操作上下文中。

3.初始和派生规则(Initial and Derived Value)

OCL表达式可以通过初始和派生规则为属性或关联角色指定初始值和派生值。OCL定义了init和derive两个关键字来定义初始和派生规则。定义初始和派生规则的语法规则的语法形式如下。

注意:上述表达式中的“--”表示注释。

4.导航(Navigation)

在OCL表达式中,除了上下文之外,还可以使用与上下文相关联的模型元素,以此为上下文定义与关联元素相关的约束或规则。

OCL将上下文与相关元素之间的关系称为导航(Navigation)。OCL导航是通过使用导航运算符(圆点(.)和箭头(->))实现的。这两种导航运算符的区别在于圆点(.)适用于单个对象,而箭头(->)适用于对象集合,即两个运算符的逻辑层次是不同的。

使用导航运算符,可以引用上下文的属性、操作和关联等,这使得OCL表达式具有了十分强大的表现能力。

例如,对于图2-29中的类图,指定“订单客户必须大于18岁”这样一个约束。

图2-29 使用关联角色名的导航

表达式使用Order作为上下文,通过关联角色customer_of_order,再引用age属性,指定了“客户年龄大于18岁”这样的约束。

表达式中的导航从self关键字(Order类实例引用)开始,导航到customer_of_order,关联角色名,然后再导航到订单的属性age。

这个表达式的语义是“每个订单的客户的年龄必须大于18岁”。这里引申出了一个问题,这个规则的语义和“客户年龄必须大于18岁”的语义是一样的吗?

比上述OCL表达式更简洁的写法可表示如下。

值得注意的是:这两种写法并不完全等价,前一种写法的语义可能更具有合理性。

还有一个问题是关联角色的多重性问题。当关联角色的多重性是1时,这个关联角色代表的是一个单独的对象。但多重性大于1时,这个关联角色所代表的则是一个对象集合。二者在OCL表达式中是有显著的区别的。

例如,多重性大于1时的关联角色的OCL表达式。

式子中关联角色orders的多重性是*,表示的是与Customer关联的所有订单的集合。此时应使用箭头运算符进行导航。上面的表达式准确地表达了这样的情形。其中的size是集合类型Collection的一个属性,返回的是集合的元素个数。

最后,当UML模型忽略了关联角色名时,可以使用被关联的类名的小写形式代替关联角色名进行导航。

例如,图2-30给出了一个省略了关联角色名的类图。对这样的情形,OCL规定使用小写的类名替代关联角色名,此时的OCL表达式就可以写成下面的形式。

表达式中的order不是类名,而是图中关联在Order类一端的关联角色名。并且,这个关联角色的多重性还是*,因此使用了箭头导航符,size返回的是集合的元素数量。

图2-30 忽略关联角色名时的导航