GrabDuck

Объектный язык ограничений (и немного про метамодели)

:

image

На наш взгляд, объектный язык ограничений (Object Constraint Language, OCL) должен знать каждый, кто занимается моделированием или кому интересна модельно-ориентированная разработка. Однако, он незаслуженно обделен вниманием в сети вообще, а, уж, в русскоязычном сегменте информации просто мизер. Что это за язык и зачем он нужен, описано в этой статье. Статья не претендует на фундаментальность, полноту охвата, точность определений и т.п. Её задача: 1) на простых примерах познакомить с OCL тех, кто никогда не слышал об этом языке, 2) а для тех, кто о нём слышал, возможно, открыть новые способы его применения.

Структурные и дополнительные ограничения


Начнём сразу с примера. Допустим мы разрабатываем программу типа Jira для учета проектов, задач, для распределения этих задач по сотрудникам. Модель данных для такой программы очень упрощенно может выглядеть так.

image

Нарисовав такую диаграмму, мы наложили ограничения на нашу предметную область: зафиксировали, что в ней могут существовать только сотрудники, задачи и проекты, что они обладают именно такими атрибутами, что они могут быть связаны именно такими связями. Например, в нашей предметной области у сотрудника обязательно должны быть ФИО и дата рождения. А у задачи или проекта таких атрибутов быть не может. Или исполнителем у задачи может быть только сотрудник, но не может быть исполнителем другая задача или проект. Это очевидные вещи, но важно понимать, что, создавая такую модель, мы именно формулируем ограничения. Такие ограничения иногда называют структурными ограничениями.

Однако, часто структурных ограничений при моделировании предметной области недостаточно. Например, нам может потребоваться ограничение, что руководитель проекта не может быть одновременно и участником проекта. Сейчас из этой диаграммы такое ограничение никак не следует. Приведем другие примеры (с подвохом ;-) ) дополнительных (не структурных) ограничений:

  1. Стажер не может руководить проектами
  2. Программист может руководить одним проектом, но при этом не может участвовать в других проектах
  3. Ведущий программист может руководить не более чем двумя проектами одновременно
  4. У проекта должен быть только один руководитель
  5. В одном проекте не могут участвовать полные тезки
  6. В закрытом проекте не может быть открытых задач (задача считается закрытой, если у неё выставлено фактическое время выполнения)
  7. Перед назначением исполнителя для задачи должно быть определено планируемое время выполнения
  8. Перед закрытием для задачи должно быть определено планируемое время выполнения
  9. У сотрудника в одном проекте может быть только одна открытая задача и не более 5 задач по всем проектам
  10. Сотрудник должен быть совершеннолетним
  11. ФИО сотрудника должно состоять из трёх частей (фамилия, имя и отчество), разделенных пробелами (пробелы не могут быть двойными, тройными и т.п.)
  12. Ведущего программиста не могут звать Зигмунд
  13. Фактическое время работы над задачей не может превышать планируемое более, чем в два раза
  14. Задача не может быть своей подзадачей
  15. Планируемое время выполнения задачи должно быть не меньше, чем время, запланированное на подзадачи
  16. … придумайте сами несколько ограничений ...

Резюме

Различные языки моделирования позволяют описывать структурные ограничения, накладываемые на предметную область:

  • на виды объектов: объект может быть только экземпляром соответствующего класса (сотрудник не может обладать свойствами проекта, задачу нельзя сохранить в таблицу с сотрудниками и т.п.);
  • на допустимые свойства и ассоциации;
  • на типы: свойства могут принимать значения только определенного типа;
  • на множественность: значения обязательных свойств/ассоциаций должны быть указаны, для свойств/ассоциаций с множественностью «1» не может быть указано несколько значений и т.п.

Дополнительные ограничения можно описывать с помощью дополнительных языков, таких как OCL.

Настройка Eclipse


Если вы хотите попробовать всё это на практике, то потребуется Eclipse. Если нет, то переходите к следующему разделу.
  1. Скачайте и распакуйте Eclipse, желательно Eclipse Modeling Tools. Если вы уже используете Rational Software Architect 9.0 (который основан на Eclipse), можете использовать его.
  2. Установите необходимые библиотеки:
  3. Установите плагины pm*.jar с тестовой метамоделью:
    • Для Eclipse. Скопируйте эти файлы в папку «$ECLIPSE_HOME/dropins»
    • Для RSA 9.0. Скопируйте эти файлы в папку «C:\Program Files\IBM\SDP\plugins\» (в зависимости от особенностей установки папка может быть немного другая). Папку dropins не рекомендуется использовать из-за каких-то багов.
  4. Перезапустите Eclipse. Выберите в меню File -> New -> Other… В строке поиска напишите «pm». Должны появиться «Pm Model» и «Pm Diagram».
  5. Вы можете либо самостоятельно создать тестовую модель, либо взять готовую
  6. Откройте консоль: Window -> Show View -> Other… Найдите в списке «Console».
  7. В окне консоли раскройте выпадающий список «Open Console» и выберите в нём «Interactive OCL» (см. рисунок ниже).
  8. Выберите любой объект на диаграмме и напишите в нижней части консоли «self», нажмите «Enter». Если всё настроено правильно, то вы увидите нечто подобное:

Если у вас не работает плагин с метамоделью, можете собрать его самостоятельно из исходных кодов. В нем используется относительно древняя версия GMF, чтобы он работал в RSA 9.0.

Пример № 1. Планируемое время выполнения задачи должно быть не меньше, чем время, запланированное на подзадачи


Первая вещь, которую вы должны знать об OCL, это то, что ограничение всегда относится к конкретному классу объектов. Перед написанием OCL-правила нам нужно выбрать этот класс. Иногда этот выбор не очень очевиден, но сейчас всё просто: правило относится к задаче. Мы можем обратиться к конкретному экземпляру задачи с помощью переменной self. Если необходимо получить значение некоторого свойства задачи, то после имени переменной пишем точку и затем имя этого свойства. Аналогично можно указать имя ассоциации. Попробуйте сделать это в Eclipse.
Примечание

Чтобы избежать лишних проблем, в модели все классы, свойства и т.п. названы с использование символов латинского алфавита. Узнать как именно называется то или иное свойство вы можете с помощью автодополнения (вызывается нажатием Ctrl + SPACE).


Итоговое правило контроля показано на рисунке.

Не очень тривиальная вещь для новичков в OCL – это разница между символами «.» и «->».
То, что идет после точки, относится к каждому элементу из коллекции значений или объектов.
То, что идет после стрелки, относится ко всей коллекции значений или объектов.
В таблице приведены некоторые примеры использования точки и стрелки.

OCL-выражение Интерпретация OCL-выражения
self.План_время Получить значение свойства «План_время»
self.Подзадачи Получить множество подзадач
self.Подзадачи.План_время Для каждой подзадачи в множестве получить значение свойства «План_время» и в итоге получить множество таких значений
self.Подзадачи.План_время->sum() Посчитать сумму на всем множестве значений
self.План_время->sum() Несмотря на то, что у задачи может быть указано максимум одно значение для свойства «План_время», это значение неявно преобразуется в множество (с единственным элементом). К полученному множеству применяется операция sum()
Примечание

Посмотрите в спецификации OCL описание операций collect() и oclAsSet().


Пример № 2. Руководитель проекта не должен быть участником проекта


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

Переменную self можно не указывать, она подразумевается неявно. Однако, важно отметить, что некоторые операции (select, exists, forAll, ...) создают новую область видимости с дополнительными неявными переменными (итераторами). При этом достаточно затруднительно понять к какой именно неявной переменной относится цепочка свойств. В таких ситуациях крайне желательно все переменные (или все кроме одной) указывать явно.

Пример № 3. Ведущий программист может руководить не более чем двумя проектами одновременно


Новички при написании подобных правил обычно первым делом спрашивают есть ли в OCL if-then-else. Да, есть, но в большинстве правил вместо него лучше использовать импликацию.

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

Пример № 4. В одном проекте не могут участвовать полные тезки


Вот, пожалуй, первое нетривиальное правило. Можно попытаться его сформулировать начиная как с сотрудника, так и с проекта. Важно начать с правильного класса :-) Если вы не знаете о существовании операции isUnique(), то можете начать городить достаточно монстроидальные конструкции, как я, когда впервые столкнулся с такой задачей.

Уберите из правила явное объявление итератора (переменная «у»), и попробуйте интерпретировать правило. Лично я не помню точно что произойдёт. Может быть будет ошибка, что невозможно однозначно определить о каком именно ФИО идет речь. А может быть итератор имеет приоритет над self. Нужно смотреть спецификацию. В любом случае, желательно избегать таких ситуаций, и указывать переменные явно.

Примечание

Итераторов может быть более одного. Посмотрите спецификацию OCL, поэкспериментируйте в консоли OCL. Посмотрите как в спецификации определена операция isUnique().


Пример № 5. У сотрудника в одном проекте может быть только одна открытая задача и не более 5 задач по всем проектам


В данном правиле мы сначала объявляем переменную с перечнем открытых задач. Затем проверяем для них два условия. Будем считать, что задача считается открытой, если для неё не указано фактическое время выполнения.

Как вы считаете, для какого класса лучше определять это правило? Для сотрудника или задачи?

Пример № 6. Задача не может быть своей подзадачей


Если вы не сразу поняли суть правил, изображенных на рисунке, то это вполне естественно. С одной стороны, есть класс объектов «Задача», с другой – у задачи есть ассоциация «Задача», которая указывает на родительскую задачу. Желательно именовать классы и свойства/ассоциации по-разному, это повышает качество модели и упрощает понимание OCL-правил.

Начнем с реализации правила «в лоб». Проверим, не указывает ли ассоциация «Задача» у некоторой задачи на саму задачу: «self.Задача <> self». На рисунке мы убрали явное указание на переменную self. Казалось бы, в этом нет ничего фатального, однако в OCL-выражениях мы можем ссылаться не только на свойства или ассоциации, но и на классы. Например, с помощью такого выражения мы можем получить перечень всех задач: «Задача.allInstances()». В данном примере, интерпретатор OCL скорее всего решит, что речь идет об ассоциации «Задача», а не о классе. Однако, повторюсь, желательно избегать такие неоднозначности.

Второй недостаток этого правила заключается, в том, что оно не учитывает, что у подзадач могут быть свои подзадачи. Задача может запросто оказаться подзадачей своей подзадачи. Чтобы убедиться, что это не так нам могли бы потребоваться циклы или рекурсивные функции. Но циклов в OCL нет, а с функциями всё не очень просто. Зато есть замечательная операция closure(). Эта операция определена для коллекций, поэтому перед ней необходимо ставить стрелочку, а не точку. Для каждого элемента коллекции операция closure() вычисляет выражение, указанное в качестве её аргумента, затем объединяет полученные значения в коллекцию, для каждого элемента которой снова вычисляет это выражение, добавляет полученные значения к результирующей коллекции и так далее:

self.Задача->union(self.Задача.Задача)->union(self.Задача.Задача.Задача)->union(...)

На рисунке приведены два варианта правила, основанные на рекурсии по родительским задачам и подзадачам соответственно. Как вы считаете, какой вариант лучше (вопрос с подвохом)?
Примечание

Наверняка, рано или поздно вам потребуются циклы. В большинстве случаев, вместо них можно использовать операции, определенные для коллекций (select, exists, ...).

Если всё-таки нужен цикл, то можно попробовать что-то подобное:

Sequence{1..10}->collect(...)

Также обратите внимание на операцию iterate().

Пример № 7. ФИО сотрудника должно состоять из трёх частей (фамилия, имя и отчество), разделенных пробелами (пробелы не могут быть двойными, тройными и т.п.)


Очевидно, что подобное правило проще всего было бы реализовать с помощью регулярных выражений. Однако, в спецификации OCL нет операций для работы с ними. На рисунке вы видите примеры реализации такого правила без использования регулярных выражений. Хорошая новость заключается в том, что стандартная библиотека OCL расширяемая, и в некоторых реализациях операция matches() всё-таки есть, либо есть возможность реализовать её самостоятельно.

Конструкции OCL


Все OCL-выражения, которые мы с вами рассмотрели, написаны на Basic OCL (Essential OCL). (Кстати, как вы думаете, чем они отличаются?) Перечислим основные конструкции, которые мы использовали:
  • Переменная self – выражение всегда привязано к определенному классу объектов
  • Обращение к свойствам (и ассоциациям) по имени:
    • self.Исполнитель.Задачи.Проект.Руководитель.Задачи...
  • Арифметические и логические операции
  • Вызов функций:
    • ФИО.substring(1, 5)
    • ФИО.size() = длина строки
  • Работа с коллекциями:
    • ФИО->size() = 1, т.к. у сотрудника должно быть единственное ФИО
    • Задачи->forAll(задача | задача.План_время > 10)
  • Объявление переменных:
    • let s1: Integer = ФИО.indexOf(' ') in
  • Условный оператор (if-then-else)

В Complete OCL есть некоторые дополнительные конструкции, которые мы не будем подробно рассматривать, вы можете самостоятельно познакомиться с ними, прочитав спецификацию:
  • Сообщения, состояния
  • Пакеты, контекст, назначение выражения (package, context, inv, pre, post, body, init, derive)

Домашнее задание


Посмотрите спецификацию OCL.

Разберитесь с примитивными типами данных, с видами коллекций (множество, последовательность и т.д.). Как вы думаете почему в OCL нет примитивных типов для работы с датой и временем? Что делать, если они всё-таки нужны?

Разберитесь чем отличаются Basic OCL, Essential OCL, Complete OCL.

Реализуйте самостоятельно другие OCL-правила, проверьте их в Eclipse.

Все OCL-выражения, которые приведены в статье могут принимать только истинное или ложное значение. (Кстати, согласны ли вы с этим утверждением?) Как вы считаете могут ли OCL-выражения иметь числовую область значений, возвращать строки, множества значений или объектов? Как можно было бы использовать такие не булевы OCL-выражения?

Как вы считаете, может ли результатом вычисления OCL-выражения быть другое OCL-выражение?

Бонус. Немного про метамодели


Всё, что мы с вами до этого делали – это описывали ограничения нашей модели и проверяли эти ограничения на конкретных сотрудниках, задачах, проектах. Например, мы описали правило, что руководитель проекта не может быть одновременно и участником проекта. Причём мы описали это правило для всех проектов и сотрудников вообще. А затем проверяли его для конкретных проектов и сотрудников.
Пример № 1. Метамодель «Сотрудник-Задача-Группа»

Теперь давайте поднимемся на уровень выше. Например, мы хотим разработать ещё одно приложение – для службы заказа такси. Там будет аналогичная модель данных. Вместо сотрудников пусть будут водители, вместо задач – заказы, а вместо проектов – города. Конечно это очень условный пример, но он нужен только для демонстрации идеи. Или, допустим, нам нужно разработать приложение для записи на прием к врачу. Там будут врачи, отделения и приемы.

Мы можем посмотреть на все эти модели, и увидеть в них общие закономерности. Программист, водитель и врач – это по сути одно и тоже – просто сотрудник. Заказ, прием – это, если обобщить, то просто задача. Ну и обобщим город, отделение, проект. Назовем их просто группой. Связь между сотрудником и задачей назовем выполнение. Связь между сотрудником и группой назовем участие.

Нарисовав такую картинку, мы с вами поднялись на новый уровень осознания предметной области – разработали метамодель, язык, на котором можем описывать любые подобные модели.

Напомню, что модель, которую мы построили в первой части статьи, накладывала некоторые структурные и неструктурные ограничения на сведения об объектах в нашей предметной области.

Аналогично и метамодель накладывает структурные и неструктурные ограничения на модели, которые строятся в соответствии с этой метамоделью. Например, в любой модели, которая соответствует данной метамодели, могут быть только элементы, которые основаны на метаклассах, отмеченных красным цветом на рисунке. В модели не может появиться, например, класс «Здание», «Дорога» или что-то подобное, не являющееся сотрудником, задачей и т.п.

Примечание

К слову, метамодели строятся на основе метаметамоделей (например, MOF, Ecore). Если вам это интересно, почитайте спецификацию OMG MOF. В общем случае, может быть произвольное количество уровней моделирования, однако обычно достаточно 2-3.

Кстати, как вы считаете на основе какой метаметаметамодели построена сама MOF?

Если вы не боитесь вывихнуть мозг, то почитайте статью Dragan Djuric, Dragan Gaševic и Vladan Devedžic «The Tao of Modeling Spaces» (лично у меня вывих мозга уже от их фамилий). По большому счету, при разработке программного обеспечения всё является моделью (от мысленного образа в сознании разработчика до исходного кода, документации, сценариев тестирования и т.п.), а процесс разработки – это преобразование одних моделей в другие. Тему преобразования моделей мы постараемся раскрыть в последующих статьях.


Как вы считаете, соответствует ли модель, изображенная на рисунке выше (зеленые прямоугольники, синие линии), метамодели (красные прямоугольники)?

Очевидно, что кроме структурных ограничений метамодель может содержать и неструктурные ограничения. Последние мы можем описывать опять-таки на языке OCL. Примеры подобных правил приведены на рисунке.

Примечание

На самом деле этот рисунок не очень корректный. Вместо полноценной метамодели тут используется UML-профиль, который, строго говоря, является обычной UML-моделью, построенной на основе метамодели UML (которая, в свою очередь, построена на основе метаметамодели MOF). Однако, по сути, метамодели часто строятся не с нуля, а в виде UML-профиля. Например, в стандарте ISO 20022 метамодель реализована в двух равнозначных вариантах: 1) полноценная метамодель, основанная на Ecore, и 2) UML-профиль. С точки зрения целей данной статьи эти нюансы не очень существенны, поэтому будем считать UML-профиль «метамоделью».


Пример № 2. Метамодель «Сущность-Атрибут-Связь»

Описанная выше метамодель выглядит немного искусственной и бесполезной. Попробуем построить более адекватную метамодель. Допустим, нам необходимо разработать язык, который позволяет описывать структуру данных в реляционной базе данных. При этом разница между участниками, задачами, проектами и т.п. не так важна. Всё это сущности, у которых могут быть атрибуты, и которые связаны между собой связями. В нашем примере есть два вида связей: один-ко-многим, многие-ко-многим. Метаклассы такой метамодели описаны на рисунке красными прямоугольниками. Как вы считаете, соответствует ли модель, изображенная на рисунке (зеленые прямоугольники, синие линии), метамодели? Каждый ли элемент этой модели можно отнести к одному из метаклассов?

Теперь перейдём, наконец, к практике. Для этого я буду использовать Rational Software Architect, вы можете использовать любой вменяемый UML-редактор. Можно было бы показать всё на примере бесплатного и идеологически правильного Papyrus, но, к сожалению, он не очень удобный.

Итак, создайте в UML-редакторе новый пустой проект. Создайте в нем UML-профиль со следующими стереотипами.

Примечание

Обратите внимание на свойства стереотипа «Attribute». Это перечень свойств, которыми может обладать любой атрибут в нашей модели.


Затем создайте следующую UML-модель, примените к ней только что созданный профиль, примените нужные стереотипы. Если лень всё это создавать, можете взять готовый проект.

Грубо говоря, мы построили модель в соответствии с нашей «метамоделью». Каждый элемент нашей модели является «экземпляром» некоторого метакласса из «метамодели».

Примечание

Ещё раз повторюсь, что, строго говоря, это не так. И профиль, и стереотипы, и модель, и все элементы модели – всё что мы с вами создали – это экземпляры метаклассов из метамодели UML. Т.е. и профиль, и модель, которая его использует, находятся на одном уровне моделирования. Но с практической точки зрения, мы можем считать этот профиль «метамоделью», в соответствии с которым создана наша модель. Если вы понимаете о чём идет речь, то до понимая того что такое метамодели вам остается ещё один шаг – понять чем являются прямоугольники и линии на диаграммах, и понять чем является xmi-файл с точки зрения моделирования. В этом вам поможет статья, которая упоминалась выше.


Теперь опишем некоторые дополнительные ограничения нашей метамодели на языке OCL. Например, атрибуты должны принадлежать сущностям. Если бы мы создавали метамодель в виде полноценной метамодели, а не профиля, то такое правило выглядело бы очень просто:
owner.oclIsKindOf(Entity)

Более того, нам даже не пришлось бы описывать такое правило. Мы бы просто создали ассоциацию owner между Attribute и Entity, которая в принципе не может связывать элементы других видов.

Однако, наша метамодель создана в виде UML-профиля, поэтому правило необходимо и, на первый взгляд, оно выглядит не очень тривиально:

base_Property.class.getAppliedStereotype('PMProfile::Entity') <> null

Это правило относится к стереотипу «Attribute», который расширяет UML-метакласс «Property». Это значит, что в модели мы можем к некоторому свойству (экземпляру UML-метакласса «Property») привязать экземпляр стереотипа «Attrbiute». У последнего мы можем задать определенные значения minLength, maxLength и pattern. OCL-правило, которое мы написали выше, будет проверять как-раз этот экземпляр стереотипа «Attribute».

Через свойство base_Property мы переходим от экземпляра стереотипа к свойству. Затем через ассоциацию class переходим к классу, которому принадлежит свойство. И, наконец, проверяем, применен ли к этому классу стереотип «Entity».

Примечание

Не знаю на сколько это соответствует спецификации, но иногда ассоциацию между экземпляром стереотипа и экземпляром UML-метакласса (в данном случае, base_Property) можно не указывать, она будет неявно подразумеваться так же как self.


Примечание

Если у вас возник вопрос, откуда я узнал, что нужно использовать именно ассоциацию class, то есть два пути: 1) автодополнение в Eclipse (Ctrl + SPACE) и 2) спецификация OMG UML.


В правиле выше используется стандартный прием для UML-редакторов, основанных на Eclipse. Однако операция getAppliedStereotype() является нестандартным расширением OCL, в других инструментах она может не поддерживаться. Это же правило можно записать следующим образом:
class.extension_Entity->notEmpty()

Проверяем есть ли у класса, которому принадлежит свойство, связь (через ассоциацию extension_Entity) с расширением, основанным на стереотипе «Entity».
Примечание

2-ой вариант теоретически выглядит более соответствующим стандарту. Однако в старых версиях Eclipse с ним могут быть проблемы, для них рекомендуется использовать 1-ый вариант.


Примечание

Попробуйте найти ассоциации base_Property, extension_Entity и class в профиле.


На рисунке изображены ещё три правила
  1. У атрибута должен быть указан тип.
  2. У отношения один-ко-многим множественность на одном конце должна быть не больше 1, а на другом конце – больше 1.
  3. Все свойства, принадлежащие сущности, должны быть либо обычными атрибутами, либо ролями отношения.

Как вы считаете, можно было бы заменить эти правила на обычные структурные ограничения, если бы мы создавали модель не в виде UML-профиля, а в виде метамодели, основанной на MOF?

MOF и Ecore очень похожи друг на друга. Можете ли вы представить какую-то принципиально иную метаметамодель?

Примеры ограничений на уровне метамодели

Ниже приведены примеры самого простого и самого сложного правил из одной реальной метамодели, которую мы создавали. С одной стороны, это конечно пример ужасного кода, но с другой – демонстрация некоторых конструкций OCL :-)

Максимальная повторяемость компонента АТД должна быть больше 0:

upper > 0

Компонент АТД, который унаследован через отношение «ограничение», должен находиться на той же позиции, что и соответствующий ему компонент родительского типа:
datatype.generalization->exists(
  getAppliedStereotype('SomeProfile::restriction') <> null)
implies (
let parent : DataType = datatype.general->any(true).oclAsType(DataType) in
let props : OrderedSet(Property) = parent.ownedAttribute->
  select(getAppliedStereotype('SomeProfile::Component') <> null) in
let cur : Property = props->select(x|x.type=self.type)->any(true) in
cur <> null implies (
let prevEnd : Integer = props->indexOf(cur) - 1 in
prevEnd = 0 or (
  let allPrev : OrderedSet(Property) = props->subOrderedSet(1, prevEnd) in
  let requiredPrev : OrderedSet(Property) = allPrev->select(lower > 0) in
  requiredPrev->isEmpty() or (
    let prevStart : Integer = allPrev->indexOf(requiredPrev->last()) in
    let allowedPrev : OrderedSet(Property) = allPrev->
      subOrderedSet(prevStart, allPrev->size()) in
    let index : Integer = datatype.ownedAttribute->
      indexOf(self.oclAsType(Property)) in
    index > 1 and (
      let selfAllPrev : OrderedSet(Property) = datatype.ownedAttribute->
        subOrderedSet(1, index - 1)->
        select(getAppliedStereotype('SomeProfile::Component') <> null) in
      selfAllPrev->isEmpty() or (
        let prevType : Type = selfAllPrev->last().type in
        allowedPrev->exists(x|x.type=prevType)))))))

Зачем нужен OCL


Правила контроля в профиле или метамодели – об этом вы уже всё знаете.

Правила контроля в модели – и об этом тоже.

Спецификация операций и вычисляемых свойств – с этим можете самостоятельно познакомится, прочитав спецификацию OCL.

Преобразование моделей ( QVT, MOF M2T) – об этом мы расскажем в следующих статьях.

Познание смысла бытия – для этого помедитируйте на первый рисунок. Где бы вы нарисовали Иисуса? А Матрицу?