de_DEen_USes_ESfa_IRfr_FRhi_INid_IDjapl_PLpt_PTru_RUvizh_CNzh_TW
Table of Contents hide

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

Objct Constraint Language (OCL)


1. Введение в OCL

Зачем нам нужен OCL?

Хотя диаграммы UML (например, диаграммы классов) отлично подходят для визуализации структуры, они не могут выразить все важные аспекты спецификации.

  • Естественный язык неоднозначен.

  • Традиционные формальные языки однозначны, но слишком сложны для средних модельеров систем, чтобы их читать и писать.

  • OCL закрывает этот разрыв: это формальный язык используется для выражения точных ограничений, но при этом остается простым для чтения и написания.

Основные характеристики OCL

  1. Язык чистых выражений: Выражения не имеют побочных эффектов. Оценка выражения OCL возвращает значение, но не может изменить состояние системы. Однако оно может описать изменение состояния (например, в постусловии).

  2. Не язык программирования: Вы не можете писать логику программы, поток управления или вызывать процессы/операции, изменяющие состояние.

  3. Язык с типами: Каждое выражение имеет тип. Корректные выражения должны соблюдать правила соответствия типов. Каждый классификатор UML является типом в OCL.

  4. Оценка мгновенного действия: Состояние объектов не может измениться во время оценки выражения OCL.


2. Основной синтаксис и семантика

Контекст и self

Каждое выражение OCL оценивается в контексте конкретного классификатора UML (класс, интерфейс и т.д.).

  • контекст: Определяет класс, к которому применяется ограничение.

  • self: Зарезервированное слово, указывающее на конкретный экземпляр класса контекста, который оценивается.

контекст Человек
-- 'self' указывает на конкретный экземпляр Человека

Комментарии и операторы

  • Комментарии: Обозначается как -- (например, -- это комментарий).

  • Инфиксные операторы: Вы можете использовать стандартные математические/логические операторы (+-=<). Концептуально, a + b идентично a.+(b).

Доступ к свойствам

Используйте точечную нотацию для доступа к атрибутам или операциям. Скобки обязательны для операций, даже если у них нет параметров.

контекст Человек
self.age             -- Доступ к атрибуту 'age'
self.stockPrice()    -- Вызов операции 'stockPrice'

3. Типы и коллекции

Базовые предопределенные типы

OCL включает стандартные типы с конкретными операциями:

  • Логический: истиналожь (Операции: иилиневлечетисключающее или)

  • Целое / Действительное: Числа (Операции: +-*/абсмаксминделост)

  • Строка: Текст (Операции: размерконкатподстрокавЦелое)

Коллекции

Коллекции — это абстрактные типы, используемые для хранения нескольких элементов. Доступ к ним осуществляется с помощью -> обозначения.

  1. Множество: Математическое множество (без дубликатов, неупорядоченное). Множество {1, 2, 5}

  2. Упорядоченное множество: Без дубликатов, но упорядочено по позиции. Упорядоченное множество {5, 4, 3}

  3. Множество: Позволяет дубликаты, неупорядоченные. Множество {1, 2, 2, 5}

  4. Последовательность: Позволяет дубликаты, упорядоченные. Последовательность {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: Возвращает единственный объект.

  • Множественность *: Возвращает Множество (или Упорядоченное множество если отмечено {упорядочено}).

контекст 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. Пред- и постусловия (prepost)

Ограничения, связанные с операциями.

  • 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)