对象约束语言(OCL)全面指南
基于所提供的讲义材料,本指南涵盖了在UML建模背景下对象约束语言(OCL)的基本概念、语法和实际应用。

1. OCL简介
我们为什么需要OCL?
虽然UML图(如类图)在可视化结构方面非常出色,但它们无法表达规范的所有相关方面。
-
自然语言具有歧义性。
-
传统形式语言虽然无歧义,但对于普通系统建模者来说太难阅读和编写。
-
OCL填补了这一空白:它是一种形式语言用于表达精确约束,但仍然易于阅读和编写.
OCL的核心特性
-
纯表达式语言:表达式没有副作用。求值OCL表达式会返回一个值,但无法改变系统状态。然而,它可以描述状态变化(例如,在后置条件中)。
-
不是编程语言:你无法编写程序逻辑、控制流,或调用会改变状态的过程/操作。
-
类型化语言:每个表达式都有类型。合法的表达式必须遵守类型一致性规则。每个UML分类器都是OCL的一种类型。
-
瞬时求值:对象的状态在求值OCL表达式期间不能改变。
2. 基本语法和语义
上下文和Self
每个OCL表达式都在特定UML分类器(类、接口等)的上下文中进行求值。
-
上下文: 定义约束所适用的类。 -
self: 一个保留字,指代正在求值的上下文类的特定实例。
上下文 Person
-- 'self' 指代 Person 的一个特定实例
注释和操作符
-
注释: 由 表示
--(例如,-- 这是一个注释). -
中缀操作符: 您可以使用标准的数学/逻辑操作符(
+,-,=,<)。在概念上,a + b与a.+(b).
访问属性
使用点号表示法来访问属性或操作。即使操作没有参数,也必须使用括号。
上下文 Person
self.age -- 访问 'age' 属性
self.stockPrice() -- 调用 'stockPrice' 操作
3. 类型和集合
基本预定义类型
OCL 包含具有特定操作的标准类型:
-
布尔值:
true,false(操作:与,或,非,蕴含,异或) -
整数 / 实数:数字(操作:
+,-,*,/,绝对值,最大值,最小值,整除,取模) -
字符串:文本(操作:
长度,连接,子字符串,转为整数)
集合
集合是用于存储多个元素的抽象类型。它们通过 -> 符号来访问。
-
集合: 数学集合(无重复,无序)。
集合 {1, 2, 5} -
有序集合: 无重复,但按位置排序。
有序集合 {5, 4, 3} -
多重集合: 允许重复元素,无序。
多重集合 {1, 2, 2, 5} -
序列: 允许重复元素,有序。
序列 {1, 2, 5, 10}-
注意: 该
..表示法创建一个连续整数的序列:序列 {1..5}.
-
关键集合操作
-
通用:
size(),isEmpty(),notEmpty(),includes(v),excludes(v),count(v),sum(). -
迭代器(OCL中至关重要):
-
select(expr): 返回满足条件的元素,其中expr为真。 -
reject(expr): 返回满足条件的元素,其中expr为假。 -
collect(expr): 从所有元素中提取特定属性,形成一个新的集合。 -
forAll(expr): 返回true如果expr对至少一个元素为真。所有元素。 -
exists(expr): 返回true如果expr对至少一个元素为真。至少一个元素。 -
iterate(elem; acc = init | expr): 所有迭代器的通用基础。
-
迭代器语法变体:
self.employee->select(age > 50)
self.employee->select(p | p.age > 50)
self.employee->select(p: Person | p.age > 50)
4. 导航UML模型
OCL被广泛用作导航语言,用于遍历关联。
标准导航
使用相反的角色名称进行导航。返回类型取决于多重性:
-
多重性
1或0..1: 返回单个对象。 -
多重性
*: 返回一个集合(或有序集合如果标记为{ordered}).
上下文 Company
self.manager.age > 40 -- manager 是 0..1,返回一个 Person 对象
self.employee->notEmpty() -- employee 是 0..*,返回一个集合
如果缺少角色名称,请使用另一端类的名称的小写形式(例如, self.bank).
关联类
使用关联类的小写名称导航到关联类。
上下文 Person
self.job->size() >= 1 -- 'job' 是 Person 和 Company 之间的关联类
递归关联
当一个关联循环回到同一类时(例如,两个人之间的婚姻),必须使用方括号指定方向 [roleName].
上下文 Person
self.marriage[妻子]->select(m | m.结束 = false)->size() = 1
限定关联
使用方括号中的限定符属性来选择特定对象。
上下文 Bank
self.customer[12345] -- 返回账户号为12345的特定人员
self.customer -- 返回所有客户的集合
重新类型化(强制转换)
使用 oclAsType() 来访问子类型或隐藏的超类属性。
self.oclAsType(子).p -- 访问在子类型中定义的属性 'p'
self.oclAsType(超).p -- 访问在超类中定义的属性 'p'
5. 编写约束
1. 不变式(inv)
必须始终为真的条件始终 对于类的所有实例都必须为真。
上下文 Company
inv: self.员工数量 <= 50
inv SME: self.股价() > 0 -- 'SME' 是约束的可选名称
2. 前置和后置条件(pre, post)
与操作相关的约束。
-
pre: 必须为真 执行前 执行。 -
post: 必须为真 执行后执行。使用结果作为返回值。 -
@pre:在后置条件中用于引用属性的值操作开始前操作开始前。
上下文 Person::income(): Integer
pre adult: self.age >= 18
post resultOK: result < 5000
上下文 Company::hireEmployee(p: Person)
post: employee = employee@pre->including(p)
3. Body、初始和派生表达式
-
body:定义查询操作的结果(可以是递归的)。 -
init:定义属性的初始值。 -
derive:定义一个派生属性。
上下文 Person::income(): Integer
body: self.job.salary->sum()
上下文 Person::isMarried: Boolean
init: false
上下文 Company::noEmployees: Integer
derive: self.employee->size()
6. 高级OCL特性
Let和Def表达式
-
let:在单个表达式中定义局部变量。let totHours: Integer = self.worksOn->collect(hours)->sum() in totHours >= 30 -
def:定义与分类器关联的可重用变量或操作。def: hasTitle(t: String): Boolean = self.job->exists(title = t)
未定义值(三值逻辑)
如果子表达式未定义(例如,导航空关联),则整个表达式未定义。然而,布尔运算符具有短路例外情况:
-
true or <未定义>= true -
false and <未定义>= 假 -
假意味着<未定义>= 真 -
<未定义>意味着真= 真
类特征与预定义对象属性
-
allInstances(): 返回该类所有现有实例的集合。Person.allInstances()->size() <= 100 -
对象属性:
oclIsTypeOf(类型),oclIsKindOf(类型),oclInState(状态),oclIsNew(用于后置条件)。
7. 综合案例研究:公司模型
为了巩固这些概念,以下是一组应用于公司数据库模型(员工、部门、项目、依赖人)的完整性约束。
基本属性约束
-- 年龄必须大于等于18
上下文 员工 inv: self.age() >= 18
-- 入职日期必须在出生日期之后
上下文 员工 inv: self.hireDate > self.birthDate
-- 管理者开始日期必须在入职日期之后
上下文 员工 inv: self.manages->notEmpty() 意味着 self.manages.startDate > self.hireDate
导航与关系约束
-- 主管必须比员工年长且薪资更高
上下文 员工
inv: self.supervisor->notEmpty() 意味着 self.age() > self.supervisor.age()
inv: self.supervisor->notEmpty() 意味着 self.salary < self.supervisor.salary
-- 主管必须在下属之前被雇佣
上下文 员工
inv: self.subordinates->notEmpty() 意味着 self.subordinates->forall(e | e.hireDate > self.hireDate)
-- 部门经理必须是该部门的员工
上下文 部门
inv: self.worksFor->includes(self.manages.employee)
-- 项目地点必须是部门的地点之一
上下文 项目
inv: self.controls.locations->includes(self.location)
-- 员工只能在由其部门控制的项目上工作
上下文 员工
inv: self.worksFor.controls->includesAll(self.worksOn.project)
集合与迭代约束
-- 社会安全号码必须在所有员工中唯一
上下文 员工
inv: Employee.allInstances->forAll(e1, e2 | e1 <> e2 意味着 e1.SSN <> e2.SSN)
-- 依赖人的姓名和关系必须在每个员工中唯一
上下文 员工
inv: self.dependents->forAll(e1, e2 |
e1 <> e2 意味着 (e1.name <> e2.name 或 e1.relationship <> e2.relationship))
-- 派生属性:部门中的员工人数
上下文 部门
inv: self.nbrEmployees = self.worksFor->size()
-- 员工最多只能参与4个项目
上下文 员工
inv: self.worksOn->size() <= 4
使用复杂逻辑让和选择
-- 每周总工作时间必须在30至50小时之间
上下文 员工
约束: 让 totHours: 整数 = self.worksOn->收集(小时)->总和()
在 totHours >= 30 且 totHours <= 50
-- 在特定项目上,最多只能有2名员工工作少于10小时
上下文 项目
约束: self.worksOn->选择(小时 < 10)->大小() <= 2
-- 只有部门经理才能在项目上工作少于5小时
上下文 员工
约束: self.worksOn->选择(小时 < 5)->非空() 意味着
self.worksFor.manages.employee = self
-- 没有下属的员工必须在所有项目上工作 >= 10 小时
上下文 员工
约束: self.subordinates->为空() 意味着 self.worksOn->对所有(小时 >= 10)
-- 部门经理必须在部门所控制的所有项目上工作 >= 5 小时
上下文 部门
约束: self.controls->对所有(p: 项目 |
self.manages.employee.worksOn->选择(小时 >= 5)->包含(p))
防止循环(递归逻辑)
-- 员工不能监督自己
上下文 员工 约束: self.subordinates->排除(self)
-- 监督层级不能存在循环
上下文 员工
def: 所有下属 = self.subordinates->并集(
self.subordinates->收集(e: 员工 | e.所有下属))
约束: self.所有下属->排除(self)














