Неочевидный PlantUML

Неочевидный PlantUML

https://t.me/humane_analyst



Это продолжение статьи.

Страницы: 1 2 3 4


4. Управление вёрсткой

PlantUML предоставляет возможность влиять на расположение элементов, но надо признать, что возможности специфические. Однако здесь есть с чем работать.

4.1. Направление вёрстки

Элементы диаграммы по умолчанию обычно выстраиваются по вертикали сверху вниз. Если попробовать воспользоваться инструкцией left to right direction, то результат для некоторых видов диаграмм (например, для диаграмм вариантов использования) будет выглядеть лучше.

До:

@startuml
"Актор 1" --> (ВИ 1)
"Актор 2" --> (ВИ 2)
@enduml

После:

@startuml
left to right direction
"Актор 1" --> (ВИ 1)
"Актор 2" --> (ВИ 2)
@enduml
До и после: вертикальная и горизонтальная вёрстка


Однако, как уже отмечалось в п. 2.2, изменение направления вёрстки приводит к тому, что меняются местами понятия «верх» и «лево», а также «низ» и «право». Также стоит знать следующее.

Существует инструкция top to bottom direction (сверху вниз), и обычно её использовать не приходится, т.к. это и так значение по умолчанию для большинства поддерживаемых PlantUML диаграмм. Однако для ментальных карт (Mind Map) умолчания другие, и в этом случае инструкция сработает.

@startmindmap
skin rose
top to bottom direction
* Транспортное средство
** Автомобиль
***_ Легковой
***_ Грузовой
** Автобус
** Троллейбус
@endmindmap
Вертикальная вёрстка для Mind Map: непривычно, но можно


4.2. Группировка элементов

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

Группировать объекты можно разными способами. Например, можно использовать:

  • пакеты — ключевое слово package. Так поступают обычно в PDF-версии документации, но с точки зрения синтаксиса языка UML это не всегда уместно;
  • прямоугольники — ключевое слово rectangle. Этот способ выглядит как самый подходящий для группировки вариантов использования («эллипсов») в диаграммах вариантов использования;
  • специальную возможность — ключевое слово together. Этот способ хорош в случаях, когда необходимо избежать появления графических обрамлений вокруг группируемых объектов.

Рассмотрим один из примеров из стандартной документации по PlantUML.

@startuml
skin rose
left to right direction
actor "Food Critic" as fc
package Restaurant {
  usecase "Eat Food" as UC1
  usecase "Pay for Food" as UC2
  usecase "Drink" as UC3
}
fc --> UC1
fc --> UC2
fc --> UC3
@enduml
Выполнение группировки с помощью графического изображения пакета


Если в приведённый пример добавить инструкцию skinparam packageStyle rectangle, то вместо символа пакета получим рамку системы, как того и требует спецификация UML. Но я лично предпочитаю другой подход: вместо добавления skinparam packageStyle rectangle достаточно заменить ключевое слово package на rectangle. Внешне результат получится таким же, но без дополнительных ухищрений. Да и вообще, рамка, группирующая варианты использования, в UML символизирует систему, а не пакет.

@startuml
skin rose
left to right direction
actor "Food Critic" as fc
rectangle Restaurant {
  usecase "Eat Food" as UC1
  usecase "Pay for Food" as UC2
  usecase "Drink" as UC3
}
fc --> UC1
fc --> UC2
fc --> UC3
@enduml
Выполнение группировки с помощью прямоугольника или пакета в стиле прямоугольника


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

@startuml
skin rose
left to right direction
actor "Food Critic" as fc
together {
  usecase "Eat Food" as UC1
  usecase "Pay for Food" as UC2
  usecase "Drink" as UC3
}
fc --> UC1
fc --> UC2
fc --> UC3
@enduml
Выполнение группировки без дополнительного визуального выделения


Небольшое замечание. При рассмотрении первых двух способов всегда использовалось название группирующего элемента (package Restaurant и rectangle Restaurant) для обозначения имени моделируемой системы, а в третьем способе его не было (было просто together). В действительности же PlantUML позволяет убрать название и в первых двух случаях (указав просто package или rectangle), а вот добавить название при третьем способе уже не получится.

4.3. Погружение в механику

По умолчанию движок PlantUML пытается расположить все объекты на минимальном пространстве с минимальным числом пересечений линий. Но жизнь сильно разнообразней, чем самый изощрённый алгоритм, поэтому иногда результат расстановки может не отвечать нашим ожиданиям.

Чтобы победить проблему, важно понимать внутреннюю механику, а значит и первопричины того, почему что-то работает так, а не иначе. И тогда уже добиться результата будет проще. Начнём издалека, с математики.

4.3.1. Теория графов

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

  • Граф — это совокупность вершин (узлов) и рёбер (дуг), соединяющих эти вершины.
  • Диграф (ориентированный граф, или попросту орграф) — это граф, состоящий из множества вершин, соединённых направленными рёбрами.
  • Взвешенный граф — граф, каждому ребру которого поставлено в соответствие некое значение (вес ребра).
Пример диграфа с тремя вершинами


Из этих определений следует, что диаграммы, с которыми приходится иметь дело, с математической точки зрения являются (или могут быть представлены) графами, причём некоторые из них — диграфами. К примеру, классы на диаграмме классов — это вершины, а связи между ними — это рёбра.

Это обстоятельство позволило разработчикам PlantUML задействовать «под капотом» графическую библиотеку GraphViz для отрисовки объектов/элементов своих диаграмм.

4.3.2. Библиотека GraphViz

GraphViz является opensource-решением, позволяющим создавать различные графы с возможностью влиять на расположение вершин и рёбер (более подробно здесь). В GraphViz по умолчанию используется вертикальная вёрстка, но возможно использование и горизонтальной (rankdir=LR). По своему смыслу оба варианта вёрстки являются взаимоисключающими.

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

Одинаковый ранг у вершин в практическом плане означает, что эти вершины будут расположены на одной линии — на одной горизонтальной линии (условной координате y) при вертикальной вёрстке или на одной вертикальной линии (условной координате x) при горизонтальной вёрстке.

Если ранги вершин различаются, то логика следующая. При вертикальной вёрстке вершины с более высоким рангом располагаются ниже, чем вершины с более низким рангом (т.е. у них значение координаты y больше); при горизонтальной вёрстке вершины с более высоким рангом располагаются правее, чем вершины с более низким рангом (т.е. у них значение координаты x больше).

Для удобства можно использовать следующее правило: чем ближе к началу, тем ниже ранг; чем дальше от начала, тем выше ранг.

Принцип увеличения ранга при движении по условной координатной плоскости


В типовой ситуации вершине присваивается более высокий ранг, чем у всех вершин, указывающих рёбрами на него. Пример: если на вершину A3 указывают рёбра, исходящие из вершин A1 и A2, причём у вершины A1 ранг=2, а у вершины A2 ранг=10, то в общем случае вершине A3 будет присвоен ранг больший, чем max {rank(A1), rank(A2)} = max {2, 10} = 10. Это может быть 11, 12 и т.д. Конкретная величина будет определяться ещё рядом других факторов, в частности весом рёбер: чем больший вес присвоен рёбрам, входящим в вершину, тем больший ранг будет присвоен вершине A3.

В соответствии с правилом выше можно утверждать, что самой близкой к началу вершиной будет A1, а самой дальней — A3.

Но здесь есть 2 нюанса:

  • библиотека позволяет пользователю указать, какие вершины должны иметь одинаковый между собой ранг (вне зависимости от соседних вершин, весов рёбер, связывающих вершины, и др.), также доступна логическая группировка вершин графа в подграфы; в таком случае ранги остальных вершин потенциально могут быть пересчитаны, чтобы удовлетворить такому «хинту»;
  • если несколько вершин имеет одинаковый ранг, то, как отмечалось выше, они обычно выстраиваются в одну строку (или столбец — в зависимости от направления вёрстки). Но это не всё. Вершины по умолчанию выстраиваются в порядке объявления их в коде, при этом GraphViz позволяет вручную переопределить взаимное расположение вершин одного ранга.


4.3.3. PlantUML

Приведённые выше сведения позволяют по-новому взглянуть на поведение PlantUML и привычные нам операции. Имеющиеся у PlantUML возможности и ограничения часто проистекают из использования GraphViz. К примеру, при рассмотрении управления длиной стрелок говорилось, что:

  • этот механизм работает только по вертикали или горизонтали (в зависимости от направления вёрстки). А это всё потому, что только одна из двух координат на плоскости в GraphViz используется для ранжирования узлов. Вторая координата содержит объекты одного ранга;
  • количество дефисов (или точек, если мы говорим про пунктирную линию) в стрелке/линии позволяет задать длину. Это оттого, что каждый дефис, начиная со второго, на самом деле увеличивает вес ребра графа (стрелки или линии) на единицу (см. здесь). Но это только если он соответствует направлению вёрстки. Вес ребра, в свою очередь, влияет на то, какой ранг присвоить вершине графа (прямоугольнику класса на диаграмме классов, эллипсу варианта использования на диаграмме ВИ и пр.), на которую указывает ребро, а чем ранг выше, тем дальше на диаграмме эта вершина будет расположена.

Важно отметить также, что согласно документации не все диаграммы в PlantUML строятся на основе GraphViz, поэтому в ряде случаев приведённые соображения не будут работать. Так для каких видом диаграмм это точно работает? Список следующий (взято отсюда):

Означает ли это, что для других диаграмм приведённые соображения вообще не актуальны? Видится, что нет. Причина в том, что мы как потребители оперируем инструкциями PlantUML, которые уже в том или ином случае транслируются в вызовы GraphViz. Но иногда эти инструкции обеспечивают тот же результат, но без обращения к GraphViz. Можно сказать, что мы оперируем контрактом PlantUML, а реализация в каждом конкретном случае может варьироваться.

Так, невидимые стрелки можно успешно применять в диаграммах последовательности и новой диаграмме деятельности, а в п. 4.1 даже был рассмотрен пример, когда направление вёрстки изменялось в Mind Map, притом что ни в одном из названных видов диаграмм GraphViz не используется.


4.3.4. Продвинутые возможности

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

Пример 1 (ориентация сверху вниз).

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

@startuml
class Первый
class Второй
@enduml

Поэтому, когда вы добавляете class Третий расположение классов претерпевает изменение. И так постепенно доходим до 6 классов.

@startuml
class Первый
class Второй
class Третий
class Четвёртый
class Пятый
class Шестой
@enduml
Автоматическое расположение классов в зависимости от их числа


Появление второй строки с классами говорит о том, что PlantUML «жонглирует» рангами классов (вершинами графа), чтобы разместить их более компактно на экране. Но мы из своих соображений можем хотеть другое расположение.

Если нам нужно, чтобы «Второй» был левее «Первого», то в простейшем случае будет достаточно перенести объявление class Второй перед class Первый (т.к. оба объекта, имеющие один ранг, обычно отрисовываются в порядке их объявления). Но если мы планируем взять расположение классов целиком в свои руки, то придётся поступить следующим образом: указать явно, что «Второй» стоит слева от «Первого»: Первый -left- Второй.

Если мы хотим, чтобы «Пятый» был ниже «Второго», да ещё и на 2 строки ниже, то надо не просто добавить связь с направлением вниз (-down-), но и увеличить вес этой связи (ребра графа) на дополнительную единицу (т.е. нужно обеспечить +2 ранга). Это делается через дополнительный дефис: Второй --down- Пятый. Так как для направлений вниз и вправо есть сокращённая запись, можно было бы написать и так: Второй --- Пятый.

Начинаем расставлять классы


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

Класс можно «втиснуть» между двумя другими


Далее мы можем решить разместить «Шестой» над «Третьим» и связать их отношением зависимости. Для этого понадобится пунктирная линия со стрелкой. Это делается простым добавлением инструкции: Третий .up.> Шестой. После всех этих действий «Шестой» и «Четвёртый» находятся в самом верху, а «Пятый» в самом низу диаграммы.

Допустим, мы хотим сделать связь «Четвёртого» и «Пятого». Если мы напишем так: Четвёртый --> Пятый, то это укажет, что «Пятый» должен находиться на 1 строку ниже относительно «Четвёртого» (разница в 1 ранг обеспечивается вторым дефисом). Но у нас «Четвёртый» явно не был ни с кем связан (т.е. его расположение никак не фиксировалось), а «Пятый» ранее был намеренно смещён на 2 строки ниже основной группы классов. В совокупности это приведёт к тому, что «Четвёртый» изменит своё положение, сместившись вниз на 2 строки, дабы оказаться на расстоянии 1 строки от «Пятого». Но если мы не хотим изменения его положения, то достаточно указать опцию[norank], которую можно интерпретировать как «исключи эту связь из расчёта рангов, оставь эти объекты в покое». Получаем: Четвёртый --[norank]> Пятый.

В результате всех манипуляций должно получиться так.

@startuml
class Первый
class Второй
class Третий
class Четвёртый
class Пятый
class Шестой
Первый -left- Второй
Второй --down- Пятый
Второй - Третий
Третий .up.> Шестой
Четвёртый --[norank]> Пятый
@enduml

А теперь задумаемся. Мы ранее управляли взаимным расположением классов, прибегая к указанию связей (линий, стрелок, пунктиров и пр. доступных вариантов). Связи получились искусственными, они могут не отражать тех логических связей, которые мы бы реально хотели представить на диаграмме. В этом случае нам нужно скрыть лишние связи. Для простоты допустим, что лишняя у нас только одна связь, это связь между «Первым» и «Вторым» классами. Значит добавляем к ней опцию [hidden] и получаем: Первый -left[hidden]- Второй. После этого линия исчезнет, но расположение классов никак не изменится, чего и требовалось.

До и после добавления [hidden]


Но это ещё не всё. Мы можем захотеть проработать модель более глубоко и указать на диаграмме дополнительные сведения о связях: имя ассоциации и её направление, имена концов ассоциации, их кратность и видимость. PlantUML позволяет добиться и этого, хотя и не без хитростей. Рассмотрим возможный вариант решения.

@startuml
class Первый
class Второй
class Третий
class Четвёртый
class Пятый
class Шестой
Первый -left[hidden]- Второй
Второй "+Владелец \t1" --down- "+Вещь \t*" Пятый : "Владеет >"
Второй "Конец1\n0..1" x-> "+Конец2\n2..*" Третий : "\t\t\t\t"
Третий .up.> Шестой
Четвёртый --[norank]> Пятый
@enduml
Демонстрация дополнительных элементов на диаграмме классов


Вдаваться в подробности того, что означает тот или иной символ здесь не буду (лучше обратиться к описанию языка UML), но на что хочу обратить внимание. При горизонтальных связях пришлось прибегнуть к символу перевода строки \n, а при вертикальных связях — к символу табуляции \t. За счёт этого получилось разрядить представленную информацию, чтобы она не сливалась друг с другом и была визуально различима. Можете попробовать убрать эти символы и оценить изменения.

Подход можно ещё немного улучшить, если использовать инструкции !skinparam nodesep X и !skinparam ranksep Y, где X и Y — целые числа. Первая инструкция устанавливает расстояние между вершинами одного ранга, вторая — между вершинами разных рангов. При вертикальной вёрстке (умолчательное значение для диаграммы классов) эти параметры определяют расстояние между графическими изображениями классов по горизонтали (ось x) и вертикали (ось y) соответственно.


Пример 2 (ориентация слева направо).

Пусть мы хотим реализовать систему продажи товаров и для этого прорабатываем назначение проектируемой системы и её границы. В первом приближении мы разработали следующую диаграмму вариантов использования (ВИ).

@startuml
skin rose
!theme reddress-lightred
actor "Посетитель маркетплейса" as visitor
actor "Покупатель" as buyer 
rectangle "Система продажи товаров" {
  usecase "Просмотр каталога товаров" as UC1
  usecase "Изменение содержимого корзины" as UC2
  usecase "Оформление заказа" as UC3
  usecase "Оплата товаров в корзине" as UC4
}
visitor -- UC1
visitor -- UC2
buyer -- UC3
buyer -- UC4
buyer -left-|> visitor
@enduml
Диаграмма ВИ с непривычным видом


По форме получилось неплохо, причём даже удалось совместить скин с темой (о такой возможности говорилось в п. 3.1). Но внутреннее чувство прекрасного требует, чтобы диаграмма выглядела канонически: слева — акторы, справа — система. Если попытаться это решить заданием направления связей (для примера: visitor -right- UC1), то результат будет малопривлекательным. Это, конечно, можно исправить за счёт выстраивания скрытых связей между вариантами использования (чтобы они расположились в столбик), но в этот раз сделаем иначе.

Зададим инструкцию left to right direction, чтобы принципиально изменить направление вёрстки. В таком варианте границы системы снизу переместятся вправо. И результат, вроде бы, достигнут, но при горизонтальной вёрстке содержимое рамки переворачивается с ног на голову: варианты использования оказываются в нелогичном порядке. Чтобы это исправить, достаточно поменять порядок объявления вариантов использования внутри rectangle, дабы получилось следующее.

@startuml
skin rose
!theme reddress-lightred
left to right direction
actor "Посетитель маркетплейса" as visitor
actor "Покупатель" as buyer 
rectangle "Система продажи товаров" {
  usecase "Изменение содержимого корзины" as UC2
  usecase "Просмотр каталога товаров" as UC1
  usecase "Оплата товаров в корзине" as UC4
  usecase "Оформление заказа" as UC3
}
visitor -- UC1
visitor -- UC2
buyer -- UC3
buyer -- UC4
buyer -left-|> visitor
@enduml
Порядок объявления вариантов использования влияет на их расположение


Результат достигнут, но на что точно стоит обратить внимание: т.к. мы сменили направление вёрстки, то, как говорилось в п. 4.1, «верх» и «лево», а также «низ» и «право» поменялись местами. Именно поэтому в предпоследней строке осталось указание left, хотя стрелка направлена вверх. Этот факт поможет следить за мыслью в дальнейшем, а сейчас надо двигаться дальше.

Предположим, что у нас возникла потребность учесть на той же диаграмме регистрацию ранее незарегистрированного пользователя, а также отразить наполнение каталога товаров и отправку сведений о продаже в ФНС. Пойдём по шагам.

После добавления нового варианта использования usecase "Регистрация покупателя" as UC5 все ВИ внутри rectangle снова перемешиваются. По какой логике это происходит, до конца не понятно, но в такой ситуации лучше вручную расставить варианты использования в том порядке, какой хотелось бы получить по итогу. Мы хотим такой порядок: UC1, UC2, UC5, UC3, UC4.

@startuml
skin rose
!theme reddress-lightred
left to right direction
actor "Посетитель маркетплейса" as visitor
actor "Покупатель" as buyer 
rectangle "Система продажи товаров" {
  usecase "Просмотр каталога товаров" as UC1
  usecase "Изменение содержимого корзины" as UC2
  usecase "Регистрация покупателя" as UC5
  usecase "Оформление заказа" as UC3
  usecase "Оплата товаров в корзине" as UC4
}
visitor -- UC1
visitor -- UC2
buyer -- UC3
buyer -- UC4
buyer -left-|> visitor
@enduml
Иллюстрация повторного изменения порядка объявления


После перестановки строк диаграмма начинает походить на наши ожидания. Но новый вариант использования UC5 надо ещё связать с другими элементами.

Добавим к коду 2 строки: visitor -- UC5 (чтобы показать, что посетитель может изъявить желание зарегистрироваться прежде, чем начинать покупки) и UC5 .[norank]. UC3 : <<extend>> (чтобы показать, что при оформлении заказа может выявиться, что пользователь не прошёл регистрацию, и система предложит это исправить). Во втором случае мы используем отношение расширения, поэтому начертание линии сделано пунктирным (для этого вместо дефисов использованы точки), плюс использован стереотип <<extend>>, как того требует UML.

@startuml
skin rose
!theme reddress-lightred
left to right direction
actor "Посетитель маркетплейса" as visitor
actor "Покупатель" as buyer 
rectangle "Система продажи товаров" {
  usecase "Просмотр каталога товаров" as UC1
  usecase "Изменение содержимого корзины" as UC2
  usecase "Регистрация покупателя" as UC5
  usecase "Оформление заказа" as UC3
  usecase "Оплата товаров в корзине" as UC4
}
visitor -- UC1
visitor -- UC2
buyer -- UC3
buyer -- UC4
buyer -left-|> visitor
visitor -- UC5
UC5 .[norank].> UC3 : <<extend>>
@enduml
Ещё одна иллюстрация полезности опции [norank]


Стоит сказать, что если бы мы не использовали опцию [norank], то UC3 на схеме сдвинулся бы со своего места на одну позицию вправо. Причина этого кроется в рангах:

  • UC3 по рангу равен остальным ВИ (поэтому все в одном столбце), но после добавления связи на него начинает указывать UC5;
  • эта связь содержит две точки, а все дефисы и точки, начиная со второй, как говорилось в п. 4.3.3, добавляют единицу к весу ребра, который потом прибавится к рангу UC3.

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

Теперь настала пора добавить акторов «Менеджер» и «ФНС». Первый будет отвечать за наполнение каталога продуктов от лица своей компании (UC6), а второй будет получать сведения о совершённых покупках на маркетплейсе. Регистрацию менеджера и др. детали для простоты рассматривать не будем. Получаем следующий код.

@startuml
skin rose
!theme reddress-lightred
left to right direction
actor "Посетитель маркетплейса" as visitor
actor "Покупатель" as buyer 
actor "Менеджер" as manager
actor "ФНС" as tax
rectangle "Система продажи товаров" {
  usecase "Просмотр каталога товаров" as UC1
  usecase "Изменение содержимого корзины" as UC2
  usecase "Регистрация покупателя" as UC5
  usecase "Наполнение каталога товаров" as UC6
  usecase "Оформление заказа" as UC3
  usecase "Оплата товаров в корзине" as UC4
}
visitor -- UC1
visitor -- UC2
visitor -- UC5
buyer -- UC3
buyer -- UC4
buyer -left-|> visitor
UC5 .[norank].> UC3 : <<extend>>
UC4 ---> tax
UC2 -[hidden]- UC6
UC6 -- manager
@enduml
Типичное расположение элементов на диаграмме вариантов использования


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

Плюс двух новых акторов я для красоты разместил справа от рамки системы. Чтобы это стало возможным, выполнено следующее:

  • акторы размещены справа от связей с соответствующими вариантами использования (UC4 ---> tax и UC6 -- manager);
  • для связи с ФНС добавлен дополнительный дефис (итого их стало 3). Причина в том, что при двух дефисах актор tax попал бы в тот же столбец, что и UC6 (ранг UC4 + 1 ранг от веса связи с двумя дефисами с актором tax это то же самое, что и ранг UC2 + 1 ранг от веса скрытой связи с UC6).

При желании можно продолжать насыщать диаграмму деталями. Давайте рассмотрим, как можно при описании отношения расширения ВИ указать точку расширения (не самая часто используемая на практике возможность).

Сперва следует добавить в расширяемый вариант использования (UC3) точку, а затем прикрепить примечание (note), на котором указано условие расширения в фигурных скобках.

Последняя версия спецификация UML предписывает прикреплять примечание к связи между ВИ (в нашем примере это связь между UC5 и UC3) с помощью пунктирной линии. У нас же вышло несколько иначе.

@startuml
skin rose
!theme reddress-lightred
left to right direction
actor "Посетитель маркетплейса" as visitor
actor "Покупатель" as buyer 
actor "Менеджер" as manager
actor "ФНС" as tax
rectangle "Система продажи товаров" {
  usecase "Просмотр каталога товаров" as UC1
  usecase "Изменение содержимого корзины" as UC2
  usecase "Регистрация покупателя" as UC5
  usecase "Наполнение каталога товаров" as UC6
  usecase "Оформление заказа\n--\n<b>extension points</b>\nВыбор покупателя" as UC3
  usecase "Оплата товаров в корзине" as UC4
}
visitor -- UC1
visitor -- UC2
visitor -- UC5
buyer -- UC3
buyer -- UC4
buyer -left-|> visitor
UC5 .[norank].> UC3 : <<extend>>
note bottom on link : "condition: {посетитель не зарегистрирован в системе}\nextension point: Выбор покупателя"
UC4 ---> tax
UC2 -[hidden]- UC6
UC6 -- manager
@enduml
Демонстрация точки расширения (вариант 1)


Как можно заметить, PlantUML помещает примечание непосредственно на связь (графическое изображение отношения), а не прикрепляет его пунктирной линией. Возможно, получилась не самая высокохудожественная композиция, но подход вполне рабочий.

Если такой стиль показался вам не по вкусу, то могу предложить другой способ, который, впрочем, тоже не совсем соответствует спецификации UML. Можно связать примечание с обоими ВИ.

@startuml
skin rose
!theme reddress-lightred
left to right direction
actor "Посетитель маркетплейса" as visitor
actor "Покупатель" as buyer 
actor "Менеджер" as manager
actor "ФНС" as tax
rectangle "Система продажи товаров" {
  usecase "Просмотр каталога товаров" as UC1
  usecase "Изменение содержимого корзины" as UC2
  usecase "Регистрация покупателя" as UC5
  usecase "Наполнение каталога товаров" as UC6
  usecase "Оформление заказа\n--\n<b>extension points</b>\nВыбор покупателя" as UC3
  usecase "Оплата товаров в корзине" as UC4
  note "condition: {посетитель не зарегистрирован в системе}\nextension point: Выбор покупателя" as note1
}
visitor -- UC1
visitor -- UC2
visitor -- UC5
buyer -- UC3
buyer -- UC4
buyer -left-|> visitor
UC5 .[norank].> UC3 : <<extend>>
UC4 ---> tax
UC2 -[hidden]- UC6
UC6 -- manager
UC5 .. note1
UC3 .. note1
@enduml
Демонстрация точки расширения (вариант 2)


Какой из двух вариантов предпочесть, оставлю на ваше усмотрение. И на этом предлагаю пример считать законченным.



Страницы: 1 2 3 4

Report Page