UML (Unified Modeling Language) это единый графический язык для описания структуры и поведения программ. Диаграмма классов показывает классы и связи между ними: реализацию, обобщение (наследование), агрегацию, композицию и зависимость, и ее поймет любой, кто знает UML. В этой статье: что такое UML, как нарисовать диаграмму классов текстом через PlantUML в IntelliJ IDEA и как генерировать такие схемы из Java-кода автоматически.

Кратко

  • UML это стандарт Object Management Group (актуальная версия спецификации 2.5.1), а диаграмма классов лишь один из его типов диаграмм.
  • В IntelliJ IDEA Ultimate диаграмму классов строят прямо из кода: контекстное меню на классе, Diagrams, Show Diagram Popup.
  • В Community Edition диаграммы рисуют текстом через плагин PlantUML Integration (для раскладки нужен Graphviz) или в браузере на planttext.com.
  • Пять связей и их запись в PlantUML: реализация <|.., обобщение <|--, агрегация o--, композиция *--, зависимость <...
  • Агрегация: части живут и без целого (объекты в ArrayList); композиция: части не существуют без целого (Node внутри LinkedList).
  • Диаграммы можно генерировать при сборке: uml-java-doclet для Maven через mvn javadoc:javadoc, но он работает только с JDK 8.

Вступление

Я думаю, каждый слышал в детстве такую поговорку как "Семь раз отмерь, один раз отрежь". В программировании так же. Лучше всегда обдумать реализацию до того, как вы потратите время на ее исполнение. Часто приходится при реализации создавать классы, придумывать их взаимодействие. И часто визуальное представление этого может помочь решить задачу наиболее правильным образом. В этом нам и помогает UML.

Что такое UML?

Если посмотреть картинки в поисковых системах, то станет понятно, что UML это что-то про схемы, стрелочки и квадратики. Что важно, что UML переводится как Unified Modeling Language. Важно тут слово Unified. То есть наши картинки поймем не только мы, но и остальные, кто знает UML. Получается это такой международный язык рисования схем.

Цветные прозрачные фигуры (цилиндр, пирамида, куб) на листе в клетку: модель собирается из простых форм

Как гласит Википедия

UML это язык графического описания для объектного моделирования в области разработки программного обеспечения, моделирования бизнес-процессов, системного проектирования и отображения организационных структур.
Самое интересное, о чем не все задумываются или догадываются, UML имеет спецификации. Причем даже есть спецификация UML 2, актуальная версия 2.5.1. Подробнее со спецификацией можно ознакомиться на сайте Object Management Group. Собственно, эта группа и занимается разработкой спецификаций UML. Интересно и то, что UML не ограничивается описанием структуры классов. Существует множество типов UML диаграмм. Краткое описание типов UML диаграмм можно увидеть в той же Википедии: UML: диаграммы. UML также широко применяется при описании различных процессов: диаграммами последовательности, например, рисуют обмен запросами между сервисами при авторизации. Возвращаясь к использованию UML диаграмм классов, стоит отметить книгу Head First: Паттерны проектирования, в которой паттерны иллюстрируются теми самыми UML диаграммами (разбор самих паттернов есть и в отдельной статье). Выходит, что UML действительно используется. И выходит, что знание и понимание его применения довольно полезный навык.

Применение

Диаграмма классов в IntelliJ IDEA Ultimate

Разберем, как с этим самым UML можно работать из IDE. В качестве IDE возьмем IntelliJ Idea. Если использовать IntelliJ Idea Ultimate, то у нас "из коробки" будет установлен плагин "UML Support" (в свежих версиях эта функция называется просто Diagrams). Он позволяет автоматически генерировать красивые диаграммы классов. Например, через Ctrl+N или пункт меню "Navigate" -> "Class" перейдем в класс ArrayList. Теперь, через контекстное меню по имени класса выберем "Diagram" -> "Show diagram popup". В результате мы получим красивую диаграмму:
Диаграмма классов для ArrayList, построенная в IntelliJ IDEA: Iterable, Collection, List, AbstractCollection, AbstractList, Serializable, RandomAccess, Cloneable и ArrayList со стрелками наследования и реализации
Но что, если хочется самому нарисовать, да еще и нет Ultimate версии Idea? Если мы используем IntelliJ Idea Community Edition, то у нас нет другого выбора. Для этого нужно понять, как такая UML схема устроена.

PlantUML: установка в Community Edition

Для начала нам понадобится установить Graphviz. Это набор утилит для визуализации графов. Его использует плагин, который мы будем применять. После установки необходимо добавить каталог bin из каталога установленного Graphviz в переменную среды окружения PATH. После этого в IntelliJ Idea в меню выбрать File -> Settings. В окне "Settings" выбрать категорию "Plugins", открыть вкладку "Marketplace" (в старых версиях кнопка "Browse repositories") и установить плагин PlantUML Integration. Чем так хорош этот PlantUML? Диаграмма описывается обычным текстом, который можно хранить в репозитории рядом с кодом и сравнивать через diff, а для раскладки элементов PlantUML использует Graphviz и его язык описания графов "dot". Более того, все что мы ниже сделаем мы можем выполнить не только в IDE, но и в онлайн сервисе planttext.com. После установки плагина PlantUML у нас появится возможность через "File" -> "New" создавать UML диаграммы. Давайте выполним создание диаграммы типа "UML class". В ходе этого автоматически генерируется шаблон с примером. Удалим его содержимое и создадим свое, вооружившись статьей с Хабра: Отношения классов: от UML к коду. А чтобы понять, как это изобразить в тексте, возьмем мануал по PlantUML: PlantUML: диаграмма классов. В нем в самом начале представлена табличка с тем, как нужно описывать связи:
Таблица из документации PlantUML: расширение записывается как <|--, композиция как *--, агрегация как o--, рядом нарисованы стрелки
Про сами же связи можем еще подсматривать сюда: "Отношения между классами в UML. Примеры", а про наследование, композицию и агрегацию в коде есть статья на JavaRush. На основе этих материалов приступим к созданию нашей UML диаграммы. Добавим следующее содержимое, описывающее два класса:

@startuml
class ArrayList {
}
class LinkedList {
}
@enduml
Чтобы увидеть результат в Idea, выберем "View" -> "Tool Windows" -> "PlantUML". Мы получим просто два квадрата, обозначающие классы.

Реализация и обобщение

Как мы знаем, оба эти класса реализуют интерфейс List. Данное отношение классов так и называют: реализация (realization). Для изображения такой связи используют стрелку с пунктирной линией. Изобразим ее:

interface List
List <|.. ArrayList
List <|.. LinkedList
List это один из дочерних интерфейсов Collection. То есть он наследуется от Collection. Эта связь называется обобщением (generalization). Выглядит как стрелка с обычной непрерывной линией. Изобразим ее:

interface Collection
Collection <|-- List

Агрегация и композиция

Для следующего типа связи добавим в описание класса ArrayList запись о package private массиве элементов:

~Object[] elementData
Теперь мы хотим показать, что ArrayList содержит какие-то объекты. В данном случае будет тип связи агрегация (aggregation). Агрегатом в данном случае является ArrayList, т.к. он содержит другие объекты. Агрегацию мы выбираем потому, что объекты в списке могут жить и без списка: они не являются его неотъемлемыми частями. Их время жизни не привязано к времени жизни списка. Агрегат с латинского переводится как "собранный", то есть что-то, составленное из чего-то. Например, в жизни, есть насосный агрегат, который состоит из насоса и двигателя. Сам агрегат можно разобрать, оставив что-то из его составных частей. Например, чтоб продать или поставить в другой агрегат. Так и в списке. И выражается это в виде пустого ромбика у агрегата и непрерывной линии. Изобразим это следующим образом:

class Object{
}
ArrayList o- Object
Теперь мы хотим показать, что в отличие от ArrayList, класс LinkedList содержит в себе Node, контейнеры, ссылающиеся на хранимые данные. В данном случае Node являются частью самого LinkedList и не могут жить отдельно. Node не является непосредственно хранимым содержимым, а только содержит ссылку на него. Например, когда мы добавляем в LinkedList какую-нибудь строку, мы добавляем новый Node, который содержит ссылку на эту строку, а также ссылку на предыдущий и следующий Node. Такой тип связи называется композицией (Composition). Для отображения у композита (того, кто состоит из частей) рисуется закрашенный ромбик, к нему ведет непрерывная линия. Запишем теперь это в виде текстового отображения связи:

class Node{
}
LinkedList *-- Node

Зависимость

И теперь необходимо научиться отображать еще один важный тип связи, зависимость (dependency relationship). Он используется тогда, когда один класс использует другой, при этом класс не содержит в себе используемый класс и не является его наследником. Например, LinkedList и ArrayList умеют создавать ListIterator. Отобразим это в виде стрелок с пунктирной линией:

class ListIterator
ListIterator <... ArrayList : create
ListIterator <... LinkedList : create
Выглядеть после всего это будет следующим образом:
Итоговая диаграмма PlantUML: List наследует Collection, ArrayList и LinkedList реализуют List, ArrayList агрегирует Object, LinkedList содержит Node, оба зависят от ListIterator с подписью create
Детализировать можно настолько, насколько это необходимо. Все обозначения указаны тут: "PlantUML: диаграмма классов".

Пять связей в одной таблице

Сведем все связи, которые мы нарисовали, вместе:
Связь Смысл Как рисуется Запись в PlantUML Пример из статьи
Реализация (realization) класс реализует интерфейс пунктирная линия, пустой треугольник у интерфейса List <|.. ArrayList ArrayList и LinkedList реализуют List
Обобщение (generalization) наследование: класс от класса, интерфейс от интерфейса сплошная линия, пустой треугольник у родителя Collection <|-- List List расширяет Collection
Агрегация (aggregation) целое содержит части, которые могут жить и без него сплошная линия, пустой ромб у целого ArrayList o-- Object объекты в ArrayList
Композиция (composition) целое состоит из частей, которые без него не существуют сплошная линия, закрашенный ромб у целого LinkedList *-- Node Node внутри LinkedList
Зависимость (dependency) класс использует другой, не храня его и не наследуя пунктирная линия с простой стрелкой ListIterator <.. ArrayList списки создают ListIterator
Кроме того, в рисовании такой схемы нет ничего сверхъестественного, и при работе над своими задачами ее можно быстро рисовать от руки. Это позволит развить навыки продумывания архитектуры приложения и поможет выявить недостатки структуры классов на раннем этапе, а не когда вы уже потратите день на реализацию неправильной модели. Мне кажется, это неплохая причина для того, чтобы попробовать? )
Карикатура: довольный кот лежит рядом с мышеловкой, подпись «Да здравствует автоматизация труда!»

Автоматизация

Есть различные способы автоматической генерации PlantUML диаграмм. Например, в Idea есть плагин SketchIT, но рисует он их не совсем правильно. Скажем, неправильно рисуется имплементация интерфейсов (отображается как наследование). Также в интернете есть примеры того, как это встроить в жизненный цикл сборки вашего проекта. Допустим, для Maven есть пример использования uml-java-doclet. Важная оговорка: этот doclet написан под старый Doclet API (com.sun.javadoc), который в Java 9 заменили новым, а в JDK 13 удалили совсем. Сам проект честно пишет, что работает только с JDK 8 и больше не поддерживается. Так что пример ниже нужно запускать на JDK 8, а для современных версий Java диаграммы удобнее строить в IDE. Для того, чтобы показать как это, воспользуемся Maven Archetype для быстрого создания Maven проекта. Выполним команду: mvn archetype:generate На вопросе выбора фильтра (Choose a number or apply filter) оставляем default, просто нажав Enter. Это всегда будет "maven-archetype-quickstart". Выбираем самую последнюю версию. Далее отвечаем на вопросы и завершаем создание проекта:
Консоль Maven archetype:generate: ввод groupId ru.javarush.umldemo, artifactId umldemo, версии 1.0-SNAPSHOT и подтверждение Y
Так как Maven не является целью данной статьи, ответы на свои вопросы по Maven можно найти в Maven Users Centre. В сгенерированном проекте откроем на редактирование файл описания проекта, pom.xml. В него скопируем содержимое из описания uml-java-doclet installing. Используемый в описании артефакт не удалось найти в репозитории Maven Central. Но у меня заработало с этим: https://mvnrepository.com/artifact/com.chfourie/uml-java-doclet/1.0.0. То есть надо в том описании просто заменить groupId с "info.leadinglight" на "com.chfourie" и поставить версию "1.0.0". После этого можем выполнить в каталоге, где находится файл pom.xml эти команды: mvn clean install и mvn javadoc:javadoc. Теперь, если открыть сгенерированную документацию (explorer target\site\apidocs\index.html), мы увидим UML схемы. Кстати, имплементация тут уже отображается верно )

Заключение

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

Вопросы и ответы

Что такое UML простыми словами?

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

Чем агрегация отличается от композиции в UML?

Временем жизни частей. При агрегации (пустой ромб у целого) части существуют независимо: строки, которые лежат в ArrayList, никуда не денутся, если список удалить. При композиции (закрашенный ромб) части принадлежат целому и без него не имеют смысла: узлы Node создает и уничтожает сам LinkedList. В PlantUML это o-- и *--, ромб всегда рисуется со стороны целого.

Чем реализация отличается от обобщения на диаграмме классов?

Обе связи рисуются стрелкой с пустым треугольником, направленной к родителю, разница в линии. Обобщение (generalization), то есть наследование extends, рисуют сплошной линией: Collection <|-- List. Реализацию интерфейса (realization), то есть implements, рисуют пунктирной: List <|.. ArrayList. Плагины-генераторы иногда путают эти две связи, поэтому на автоматических схемах стоит проверять линии.

Как нарисовать UML-диаграмму классов в IntelliJ IDEA Community Edition?

Встроенных диаграмм там нет, они только в Ultimate. Способ из статьи: установить Graphviz и добавить его каталог bin в PATH, поставить плагин PlantUML Integration через Settings, Plugins, Marketplace, создать файл через File, New, UML class и описать диаграмму текстом между @startuml и @enduml; картинка появится в окне View, Tool Windows, PlantUML. Тот же текст можно вставить на planttext.com без установки чего-либо.

Что такое PlantUML и зачем ему Graphviz?

PlantUML это инструмент, который превращает текстовое описание диаграммы в картинку. Текст удобно хранить в репозитории рядом с кодом, сравнивать через diff и править без мыши. Для расчета расположения элементов на диаграммах классов PlantUML вызывает Graphviz и его язык dot, поэтому Graphviz нужно установить отдельно. У PlantUML есть и встроенные раскладчики для простых случаев, но с Graphviz результат надежнее.

Можно ли сгенерировать UML-диаграмму из Java-кода автоматически?

Да. Самый простой путь: IntelliJ IDEA Ultimate строит диаграмму классов из кода по контекстному меню Diagrams. Для сборки есть doclet-плагины к javadoc, например uml-java-doclet из статьи, но он работает только на JDK 8: старый Doclet API, на котором он написан, удален в JDK 13. Есть и плагины вроде SketchIT, которые генерируют PlantUML-текст, но их результат стоит проверять: реализацию интерфейса они могут нарисовать как наследование.

Читайте также