Полное руководство по языку ограничений объектов (OCL)
На основе предоставленных учебных материалов, это руководство охватывает основные понятия, синтаксис и практическое применение языка ограничений объектов (OCL) в контексте моделирования UML.

1. Введение в OCL
Зачем нам нужен OCL?
Хотя диаграммы UML (например, диаграммы классов) отлично подходят для визуализации структуры, они не могут выразить все важные аспекты спецификации.
-
Естественный язык неоднозначен.
-
Традиционные формальные языки однозначны, но слишком сложны для средних модельеров систем, чтобы их читать и писать.
-
OCL закрывает этот разрыв: это формальный язык используется для выражения точных ограничений, но при этом остается простым для чтения и написания.
Основные характеристики OCL
-
Язык чистых выражений: Выражения не имеют побочных эффектов. Оценка выражения OCL возвращает значение, но не может изменить состояние системы. Однако оно может описать изменение состояния (например, в постусловии).
-
Не язык программирования: Вы не можете писать логику программы, поток управления или вызывать процессы/операции, изменяющие состояние.
-
Язык с типами: Каждое выражение имеет тип. Корректные выражения должны соблюдать правила соответствия типов. Каждый классификатор UML является типом в OCL.
-
Оценка мгновенного действия: Состояние объектов не может измениться во время оценки выражения OCL.
2. Основной синтаксис и семантика
Контекст и self
Каждое выражение OCL оценивается в контексте конкретного классификатора UML (класс, интерфейс и т.д.).
-
контекст: Определяет класс, к которому применяется ограничение. -
self: Зарезервированное слово, указывающее на конкретный экземпляр класса контекста, который оценивается.
контекст Человек
-- 'self' указывает на конкретный экземпляр Человека
Комментарии и операторы
-
Комментарии: Обозначается как
--(например,-- это комментарий). -
Инфиксные операторы: Вы можете использовать стандартные математические/логические операторы (
+,-,=,<). Концептуально,a + bидентичноa.+(b).
Доступ к свойствам
Используйте точечную нотацию для доступа к атрибутам или операциям. Скобки обязательны для операций, даже если у них нет параметров.
контекст Человек
self.age -- Доступ к атрибуту 'age'
self.stockPrice() -- Вызов операции 'stockPrice'
3. Типы и коллекции
Базовые предопределенные типы
OCL включает стандартные типы с конкретными операциями:
-
Логический:
истина,ложь(Операции:и,или,не,влечет,исключающее или) -
Целое / Действительное: Числа (Операции:
+,-,*,/,абс,макс,мин,дел,ост) -
Строка: Текст (Операции:
размер,конкат,подстрока,вЦелое)
Коллекции
Коллекции — это абстрактные типы, используемые для хранения нескольких элементов. Доступ к ним осуществляется с помощью -> обозначения.
-
Множество: Математическое множество (без дубликатов, неупорядоченное).
Множество {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)
OCL широко используется в качестве языка навигации для перехода по ассоциациям.
Переход с использованием имени противоположной роли. Тип возвращаемого значения зависит от множественности:
-
Множественность
1или0..1: Возвращает единственный объект. -
Множественность
*: ВозвращаетМножество(илиУпорядоченное множествоесли отмечено{упорядочено}).
контекст Company
self.manager.age > 40 -- manager — 0..1, возвращает объект Person
self.employee->notEmpty() -- employee — 0..*, возвращает множество
Если имя роли отсутствует, используйте строчное имя класса на другом конце (например, self.bank).
Классы ассоциаций
Переход к классу ассоциации с использованием строчного имени класса ассоциации.
контекст Person
self.job->size() >= 1 -- 'job' — это класс ассоциации между Person и Company
Рекурсивные ассоциации
Когда ассоциация возвращается к тому же классу (например, брак между двумя людьми), необходимо указать направление с помощью квадратных скобок [имяРоли].
контекст Человек
self.брак[жена]->select(m | m.завершен = false)->size() = 1
Квалифицированные ассоциации
Используйте атрибуты-качественники в квадратных скобках, чтобы выбрать конкретный объект.
контекст Банк
self.клиент[12345] -- Возвращает конкретного Человека с accountNo 12345
self.клиент -- Возвращает множество всех клиентов
Приведение типов (преобразование)
Используйте oclAsType() для доступа к свойствам подтипа или скрытому свойству суперкласса.
self.oclAsType(Подтип).p -- Доступ к свойству 'p', определённому в подтипе
self.oclAsType(Суперкласс).p -- Доступ к свойству 'p', определённому в суперклассе
5. Написание ограничений
1. Инварианты (inv)
Условия, которые должны всегда быть истинными для всех экземпляров класса.
контекст Компания
inv: self.число_сотрудников <= 50
inv SME: self.стоимость_акций() > 0 -- 'SME' — необязательное имя для ограничения
2. Пред- и постусловия (pre, post)
Ограничения, связанные с операциями.
-
pre: Должно быть истинным до выполнения. -
post: Должно быть истинным после выполнение. Используйтерезультатдля возвращаемого значения. -
@pre: Используется в постусловиях для ссылки на значение свойства до начала операции.
контекст Person::income(): Integer
pre adult: self.age >= 18
post resultOK: результат < 5000
контекст Company::hireEmployee(p: Person)
post: сотрудник = сотрудник@pre->включающий(p)
3. Выражения тела, начальные и производные выражения
-
тело: Определяет результат операции запроса (может быть рекурсивным). -
init: Определяет начальное значение атрибута. -
derive: Определяет производный атрибут.
контекст Person::income(): Integer
тело: self.job.salary->сумма()
контекст Person::isMarried: Boolean
init: ложь
контекст Company::noEmployees: Integer
derive: self.employee->размер()
6. Расширенные возможности OCL
Выражения let и def
-
let: Определяет локальную переменную в пределах одного выражения.let totHours: Integer = self.worksOn->collect(hours)->sum() in totHours >= 30 -
def: Определяет повторно используемые переменные или операции, привязанные к классификатору.def: hasTitle(t: String): Boolean = self.job->существует(title = t)
Неопределенные значения (логика с тремя значениями)
Если подвыражение не определено (например, навигация по пустой ассоциации), то всё выражение не определено. Однако логические операторы имеют исключения с коротким замыканием:
-
true или <неопределено>= true -
ложь и <неопределено>= ложь -
ложь влечет за собой <неопределено>= истина -
<неопределено> влечет за собой истину= истина
Особенности классов и предопределенные свойства объектов
-
allInstances(): Возвращает множество всех существующих экземпляров класса.Person.allInstances()->size() <= 100 -
Свойства объектов:
oclIsTypeOf(Тип),oclIsKindOf(Тип),oclInState(Состояние),oclIsNew(используется в постусловиях).
7. Комплексное исследование случая: модель компании
Чтобы закрепить эти понятия, приведем набор ограничений целостности, применяемых к модели базы данных компании (Сотрудники, Подразделения, Проекты, Иждивенцы).
Основные ограничения атрибутов
-- Возраст должен быть >= 18
контекст Сотрудник инв: self.возраст() >= 18
-- Дата найма должна быть позже даты рождения
контекст Сотрудник инв: self.датаНайма > self.датаРождения
-- Дата начала работы менеджера должна быть позже даты найма
контекст Сотрудник инв: self.управляет->не пусто() влечет за собой self.управляет.датаНачала > self.датаНайма
-- Руководитель должен быть старше и получать больше, чем сотрудник
контекст Сотрудник
инв: self.руководитель->не пусто() влечет за собой self.возраст() > self.руководитель.возраст()
инв: self.руководитель->не пусто() влечет за собой self.зарплата < self.руководитель.зарплата
-- Руководитель должен быть принят на работу раньше своих подчиненных
контекст Сотрудник
инв: self.подчиненные->не пусто() влечет за собой self.подчиненные->для всех(e | e.датаНайма > self.датаНайма)
-- Менеджер отдела должен быть сотрудником этого отдела
контекст Отдел
инв: self.работаетВ->включает(self.управляет.сотрудник)
-- Местоположение проекта должно быть одним из местоположений отдела
контекст Проект
инв: self.контролирует.местоположения->включает(self.местоположение)
-- Сотрудник может работать только над проектами, контролируемыми его отделом
контекст Сотрудник
инв: self.работаетВ.контролирует->включаетВсе(self.работаетНад.проект)
Ограничения коллекций и итераторов
-- ИНН должен быть уникальным идентификатором среди всех сотрудников
контекст Сотрудник
инв: Employee.allInstances->для всех(e1, e2 | e1 <> e2 влечет за собой e1.ИНН <> e2.ИНН)
-- Имя и отношение иждивенца должны быть уникальными для каждого сотрудника
контекст Сотрудник
инв: self.иждивенцы->для всех(e1, e2 |
e1 <> e2 влечет за собой (e1.имя <> e2.имя или e1.отношение <> e2.отношение))
-- Выводимый атрибут: количество сотрудников в отделе
контекст Отдел
инв: self.количествоСотрудников = self.работаетВ->размер()
-- Сотрудник может работать над максимум 4 проектами
контекст Сотрудник
инв: self.работаетНад->размер() <= 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)














