Перейти к содержимому

Стереотипы и помеченные значения

Практически всегда можно придумать ситуации, когда существующие классификаторы не могут раскрыть полностью какой-то аспект модели. Для таких ситуаций UML предусматривает механизм стереотипов.

Стереотип выделяется кавычками-ёлочками (например, «device»), при этом некоторые стандартные стереотипы могут менять внешний вид элемента модели. Похожая запись используется и для ключевых слов отношений, например «extend» или «include», но это уже не обязательно стереотипы.

Помимо стереотипов, при расширении UML важны ограничения и помеченные значения. Они позволяют не только дать элементу дополнительный смысл, но и зафиксировать дополнительные свойства или правила его применения.

Многие элементы UML выглядят на диаграмме как обычный текст. Из-за этого их часто добавляют свободной подписью, хотя на самом деле это свойства модели.

Например, символы +, -, # и ~ показывают видимость (visibility). Кратности 1..* и 0..1 показывают кратность (multiplicity). Запись {readOnly} показывает модификатор свойства. {abstract} показывает свойство классификатора. Сторожевое условие (guard) в квадратных скобках является условием на ребре. Эффект (effect) после / на переходе состояния является поведением перехода.

Если просто написать такой текст рядом с линией или фигурой, картинка может выглядеть правильно. Но инструмент не всегда поймёт, что это именно кратность, сторожевое условие, видимость или ограничение (constraint). Для человека на защите разница может быть незаметна с первого взгляда, но для модели она принципиальна.

Поэтому в UML-инструментах важно пользоваться свойствами элементов. Если нужно указать кратность, лучше задать multiplicity у конца ассоциации. Если нужен стереотип, лучше назначить stereotype элементу. Если нужно сторожевое условие, лучше указать guard у соответствующего ребра или перехода.

Это особенно важно, когда модель потом переиспользуется. Например, если операция действительно добавлена в класс как операция, её можно потом использовать на диаграмме последовательности. Если она была просто написана текстом в прямоугольнике, инструмент не сможет связать с ней сообщение.

UML не пытается заранее предусмотреть все термины всех предметных областей. Вместо этого он предоставляет механизм расширения. Основные инструменты расширения — стереотипы (stereotypes), помеченные значения (tagged values), ограничения (constraints) и профили (profiles).

Стереотип (stereotype) расширяет существующий метакласс UML. Например, если в проекте нужно различать обычные компоненты, внешние сервисы и адаптеры, можно использовать стереотипы «externalService», «adapter», «gateway». Но важно помнить: стереотип не создаёт новый базовый элемент языка. Компонент со стереотипом «gateway» всё равно остаётся компонентом (component). Он сохраняет семантику компонента, но получает дополнительный проектный смысл.

Помеченное значение (tagged value) — это дополнительное свойство, которое появляется у элемента благодаря стереотипу. Например, у внешнего сервиса можно хранить provider, protocol, sla, criticality. В нотации это может выглядеть как набор фигурных скобок под именем элемента:

«externalService»
Payment Service
{provider = "Bank API"}
{protocol = "HTTPS"}
{criticality = "high"}

Профиль (profile) — это пакет расширений UML для конкретной области, технологии или метода. Профиль может определить стереотипы, свойства стереотипов и ограничения на их применение. Например, команда может договориться о профиле для архитектуры ИС, где есть стереотипы «frontend», «backend», «database», «externalSystem», «messageBroker».

Профили полезны, когда команда регулярно моделирует похожие системы. Но профиль не должен заменять понимание базового UML. Если базовая семантика нарушена, стереотип её не исправит. Нельзя нарисовать неверную dependency, назвать её «important» и считать, что модель стала корректной.

Ограничения часто удобнее писать естественным языком. Например:

{оплаченный заказ нельзя редактировать}

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

Но естественный язык бывает неоднозначным. Фраза “заказ должен быть корректным” почти ничего не говорит модели. Что значит корректный? Должна ли быть хотя бы одна позиция? Должен ли быть указан клиент? Может ли сумма быть нулевой? В таких случаях полезно хотя бы частично формализовать ограничение.

Для UML существует OCL — Object Constraint Language. В рамках этого курса не нужно глубоко изучать OCL, но полезно понимать его назначение. OCL-выражение ничего не меняет в системе. Оно проверяет условие или вычисляет значение. У него не должно быть побочных эффектов. С его помощью можно задавать инварианты, предусловия и постусловия.

Инвариант говорит, что должно быть истинно для корректного состояния объекта. Например, естественное правило “у заказа должна быть хотя бы одна позиция” можно выразить так:

context Order
inv: self.items->size() >= 1

Предусловие говорит, когда операцию можно вызвать:

context Order::cancel()
pre: self.status <> OrderStatus::Delivered

Постусловие говорит, что должно стать истинным после выполнения операции:

context Order::cancel()
post: self.status = OrderStatus::Cancelled

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