Полное руководство по использованию OCL в UML с примерами PlantUML
Введение

Хотя UML (единый язык моделирования) исключителен для визуализации структурысистемы, он не обладает точностью для выражения сложных бизнес-правил и семантики. Именно здесь вступает в игру язык ограничений объектов (OCL)вступает в игру. OCL выступает в качестве формального «клейка», который соединяет диаграммы UML с строгой, однозначной бизнес-логикой.
Это руководство объясняет, как OCL интегрируется с UML, ключевые концепции, которые вам нужно знать, и предоставляет практические PlantUMLпримеры для визуализации этих ограничений.
1. Синергия: как OCL дополняет UML
Представьте UML как «скелет» вашей системы (классы, ассоциации, состояния), а OCL — как «нервную систему» (правила, условия и логика, определяющие, как ведёт себя скелет).
Визуальное представление в UML
В стандартном UML ограничения OCL обычно представляются с помощью Примечанийпривязанных к соответствующим элементам модели (классы, операции или состояния).
-
Стереотип: Часто заметки, содержащие OCL, получают
{ограничение}стереотип. -
Контекст: Каждое выражение OCL начинается с
контекстключевого слова, указывающего, к какому элементу UML применяется правило.
2. Ключевые понятия OCL в UML
A. Инварианты класса (inv)
Инварианты — это условия, которые должны всегда быть истинными для каждого экземпляра класса, независимо от того, какие операции вызываются.
Ключевое понятие: Если инвариант нарушен, система находится в недопустимом состоянии.

@startuml
skinparam classAttributeIconSize 0
skinparam noteBackgroundColor #FFF9C4
class BankAccount {
- accountNumber: String
- balance: Real
- isOverdraftEnabled: Boolean
+ withdraw(amount: Real)
+ deposit(amount: Real)
}
note right of BankAccount
**Инварианты класса:**
context BankAccount
-- Баланс никогда не может быть отрицательным, если не включена функция превышения лимита
inv balanceRule:
self.balance >= 0 или self.isOverdraftEnabled
-- Лимит превышения лимита не может превышать 500
inv overdraftLimit:
self.isOverdraftEnabled подразумевает self.balance >= -500
end note
@enduml
B. Договоры операций (pre, post, body)
Операции (методы) могут иметь ограничения, определяющие их поведение до и после выполнения.
-
Предусловие (
pre): Что должно быть верным до начала операции. (Ответственность вызывающего). -
Постусловие (
пост): Что будет верным после завершения операции. (Обещание операции). -
Тело (
тело): Определяет точное возвращаемое значение для операций запроса.

@startuml
skinparam classAttributeIconSize 0
skinparam noteBackgroundColor #E8F5E9
class ShoppingCart {
- items: Bag<Product>
- totalAmount: Real
+ checkout(): Receipt
+ getTotal(): Real
}
note right of ShoppingCart
**Контракты операций:**
-- Предусловие: Корзина не должна быть пустой
context ShoppingCart::checkout(): Receipt
pre: self.items->notEmpty()
-- Постусловие: Корзина очищается, генерируется чек
post: self.items->isEmpty() and result <> null
-- Тело: Определение операции запроса
context ShoppingCart::getTotal(): Real
body: self.items->collect(price)->sum()
end note
@enduml
C. Производные и начальные значения (вывести, инициализировать)
Вместо хранения избыточных данных UML позволяет помечать атрибуты как {выведенные} или {начальные}, при этом OCL определяет их точное значение.

@startuml
skinparam classAttributeIconSize 0
skinparam noteBackgroundColor #E3F2FD
class Employee {
- firstName: String
- birthDate: Date
- {derived} age: Integer
- {initial} isActive: Boolean = true
}
note right of Employee
**Производные и начальные значения:**
-- Вычисление возраста динамически
context Employee::age: Integer
derive: self.birthDate.yearsSinceToday()
-- Начальное состояние при создании
context Employee::isActive: Boolean
init: true
end note
@enduml
D. Охраны машины состояний
На диаграммах машин состояний переходы могут иметьохраны—логические выражения, которые должны быть истинными для выполнения перехода. OCL — стандартный язык для написания этих охран.

@startuml
skinparam stateBackgroundColor #F3E5F5
[*] --> Created
Created --> PendingApproval : submit()
state PendingApproval {
}
PendingApproval --> Approved : [total < 10000]
PendingApproval --> Rejected : [total >= 10000]
Approved --> Shipped : ship()
Shipped --> [*]
note right of PendingApproval
**Инвариант состояния:**
context Order
inv: self.status = 'Pending' implies
self.submittedBy->notEmpty()
end note
note right of Approved
**Постусловие approve():**
context Order::approve()
post: self.status = 'Approved' and
self.approvedDate <> null
end note
@enduml
3. Комплексное исследование случая: модель компании
Применим OCL к сложной диаграмме классов UML, включающейСотрудник, Отдел, иПроект. Это демонстрирует, как OCL обходит ассоциации и использует итераторы коллекций (select, forAll, size).

@startuml
skinparam classAttributeIconSize 0
skinparam noteBackgroundColor #FFFDE7
skinparam noteBorderColor #FBC02D
class Employee {
- SSN: String
- firstName: String
- birthDate: Date
- hireDate: Date
- salary: Float
+ age(): Integer
}
class Department {
- number: Integer
- name: String
- locations: Set<String>
- {derived} nbrEmployees: Integer
}
class Project {
- number: Integer
- name: String
- location: String
}
Employee "0..*" -- "1" Department : worksFor > department
Employee "0..*" -- "0..*" Project : worksOn > project
Employee "1" -- "0..1" Department : manages > managedDept
Department "1" -- "0..*" Project : controls > projects
Employee "1" -- "0..*" Employee : supervisor > subordinates
note right of Employee
**Сложные инварианты и навигация:**
context Employee
-- 1. Правило базового атрибута
inv ageCheck: self.age() >= 18
-- 2. Навигация по ассоциациям (множественность)
inv maxProjects: self.worksOn->size() <= 4
-- 3. Использование итераторов (forAll)
-- Руководитель должен быть принят на работу до подчинённых
inv hireOrder: self.subordinates->forAll(
sub | sub.hireDate > self.hireDate
)
-- 4. Правило между ассоциациями
-- Сотрудник может работать только над проектами, контролируемыми его отделом
inv projectControl:
self.worksFor.controls->includesAll(self.worksOn)
-- 5. Уникальный идентификатор (ключ)
inv uniqueSSN: Employee.allInstances()->forAll(e1, e2 |
e1 <> e2 implies e1.SSN <> e2.SSN
)
end note
note bottom of Department
**Производный атрибут:**
context Department::nbrEmployees: Integer
derive: self.worksFor->size()
end note
note right of Project
**Инвариант проекта:**
context Project
-- Местоположение проекта должно быть одним из местоположений отдела
inv validLocation:
self.controls.locations->includes(self.location)
end note
@enduml
4. Расширенные техники OCL в UML
A. Использованиеletдля удобочитаемости
Когда выражение OCL становится слишком сложным, используйте letключевое слово для определения локальных переменных в пределах ограничения. Это делает заметку UML намного проще для чтения.

@startuml
skinparam classAttributeIconSize 0
class Employee {
- worksOn: Set<Project>
}
note right of Employee
**Использование 'let' для сложной логики:**
context Employee
-- Вычислить общее количество часов и проверить границы
inv workingHours:
let totalHours: Integer =
self.worksOn->collect(hours)->sum()
in totalHours >= 30 и totalHours <= 50
end note
@enduml
B. Обработка предыдущих значений в постусловиях (@pre)
При определении постусловия часто необходимо сравнить состояние новоесостояние с староесостоянием. OCL использует @preпостфикс для доступа к значению свойства точно таким, каким оно было до начала операции.

@startuml
skinparam classAttributeIconSize 0
class Company {
- employees: Set<Employee>
+ hireEmployee(p: Employee)
}
note right of Company
**Использование @pre в постусловиях:**
context Company::hireEmployee(p: Employee)
-- Новый набор сотрудников — это старый набор + новый человек
post: self.employees = self.employees@pre->including(p)
end note
@enduml
5. Лучшие практики использования OCL в UML
-
Давайте имена вашим ограничениям: Всегда давайте имена вашим инвариантам и условиям (например,
inv maxProjects:). Это делает их проще ссылаться в документации или матрицах трассировки. -
Используйте заметки стратегически: В PlantUML и визуальных инструментах UML не загромождайте коробку класса OCL. Размещайте OCL в соседних заметках. Это сохраняет диаграмму чистой и удобной для чтения.
-
Не перегружайте ограничения: Записывайте OCL только для правил, которые невозможно легко выразить с помощью мультиплитивности UML (например,
0..1,1..*) или базовых типов. Если правило можно показать визуально, покажите его визуально. -
Используйте итераторы коллекций: Овладейте
->select(),->forAll(),->exists(), и->collect(). Это самые мощные инструменты для навигации по ассоциациям UML в OCL. -
Проверяйте на null/пустые множества: При навигации по необязательным ассоциациям (мультиплитивность
0..1или0..*), всегда используйте->notEmpty()или->isEmpty()перед доступом к свойствам, чтобы избежать ошибок неопределённой оценки.














