Бесплатно · Без регистрации · Поддержка файлов .mmd

Редактор диаграмм классов Mermaid

Диаграмма классов показывает типы и их связи: что что содержит, что от чего наследуется, что от чего зависит. Она уместна, когда суть — форма кода: доменная модель, интерфейс расширения, дерево наследования. Если нужно показать, что происходит во время выполнения, а не как устроены типы, берите диаграмму последовательности.

Доменная модель оплаты

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

classDiagram
    class Заказ {
        +String номер
        +СтатусЗаказа статус
        +Деньги итого()
        +void добавитьСтроку(Товар т, int количество)
    }
    class СтрокаЗаказа {
        +Товар товар
        +int количество
        +Деньги сумма()
    }
    class СпособОплаты {
        <<abstract>>
        +авторизовать(Деньги сумма) bool
    }
    class БанковскаяКарта {
        +String последние4
        +авторизовать(Деньги сумма) bool
    }
    class БыстрыйПлатёж {
        +String телефон
        +авторизовать(Деньги сумма) bool
    }
    class Наличные {
        +Деньги сдача
        +авторизовать(Деньги сумма) bool
    }

    Заказ "1" *-- "1..*" СтрокаЗаказа : содержит
    Заказ --> СпособОплаты : оплачивается через
    СпособОплаты <|-- БанковскаяКарта
    СпособОплаты <|-- БыстрыйПлатёж
    СпособОплаты <|-- Наличные
Открыть в редакторе
Реклама

Разобранные примеры

1. Один класс

`+` — публичный, `-` — приватный, `#` — защищённый. Член со скобками рисуется как метод; без скобок это поле.

classDiagram
    class Пользователь {
        +String почта
        -String хэшПароля
        +bool проверить(String кандидат)
    }
Открыть в редакторе

2. Наследование и интерфейсы

`<|--` — это наследование, читается как «правый расширяет левого». Аннотация `<<interface>>` — просто пометка, а не поведение, но именно она делает диаграмму читаемой.

classDiagram
    class Репозиторий {
        <<interface>>
        +найти(String id) Сущность
        +сохранить(Сущность с) void
    }
    class РепозиторийPostgres {
        -Соединение соединение
        +найти(String id) Сущность
        +сохранить(Сущность с) void
    }
    class РепозиторийВПамяти {
        -Map хранилище
        +найти(String id) Сущность
        +сохранить(Сущность с) void
    }
    Репозиторий <|.. РепозиторийPostgres
    Репозиторий <|.. РепозиторийВПамяти
Открыть в редакторе

3. Композиция против агрегации

Разница во времени жизни. Закрашенный ромб (`*--`) означает, что часть умирает вместе с целым: удалите счёт — и его строки исчезнут. Полый ромб (`o--`) означает, что часть существует самостоятельно.

classDiagram
    class Счёт {
        +String номер
    }
    class СтрокаСчёта {
        +String наименование
    }
    class Контрагент {
        +String название
    }
    Счёт "1" *-- "1..*" СтрокаСчёта : состоит из
    Контрагент "1" o-- "0..*" Счёт : выставил
Открыть в редакторе

4. Дженерики

Тильды задают параметры типа: `Репозиторий~Пользователь~`. Вложенность тоже работает, что иногда необходимо и редко бывает хорошей идеей. Кириллица внутри дженерика работает без проблем.

classDiagram
    class Репозиторий~T~ {
        +найти(String id) T
        +все() List~T~
    }
    class Кэш~K, V~ {
        +получить(K ключ) V
        +положить(K ключ, V значение) void
    }
    class РепозиторийПользователей {
        +найтиПоПочте(String почта) Пользователь
    }
    Репозиторий~Пользователь~ <|-- РепозиторийПользователей
Открыть в редакторе

5. Заметки и направление

`direction LR` раскладывает диаграмму слева направо, что дереву наследования обычно подходит лучше, чем раскладка по умолчанию. Заметка — правильное место для ограничения, которое не влезает в прямоугольник класса.

classDiagram
    direction LR
    class ХранилищеСобытий {
        +добавить(Событие с) void
        +воспроизвести(String поток) List~Событие~
    }
    class Снимок {
        +int версия
        +byte[] содержимое
    }
    ХранилищеСобытий --> Снимок : пишет каждые 100 событий
    note for ХранилищеСобытий "Только на добавление. События никогда не меняются и не удаляются."
Открыть в редакторе

Справочник синтаксиса диаграммы классов

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

СинтаксисЗначение
classDiagramОткрывает диаграмму. Регистр важен.
class Имя { ... }Класс с членами. Закрывающая скобка — на отдельной строке.
+членПубличный.
-членПриватный.
#членЗащищённый.
+метод(Тип арг) ТипВозвратаМетод — методом его делают именно скобки.
<<interface>> / <<abstract>>Стереотип, пишется первой строкой внутри класса.
A <|-- BНаследование: B расширяет A.
A <|.. BРеализация: B реализует интерфейс A.
A *-- BКомпозиция: B не переживает A.
A o-- BАгрегация: B может существовать без A.
A --> BНаправленная ассоциация.
A ..> BЗависимость — A использует B, но не хранит его.
A "1" --> "0..*" B : подписьКратность на каждом конце плюс подпись связи.
class Репо~T~Параметр обобщённого типа.
note for A "текст"Заметка, прикреплённая к классу.
direction LRМеняет направление раскладки.
Реклама

Ошибки, ломающие диаграмму классов

Воспроизведены на Mermaid 11.12.2. Диаграмма классов — из самых снисходительных здесь типов, поэтому половина этих ошибок спокойно рисуется и выдаёт неверную картину.

Что вы видите

Parse error, заканчивается на: got 'EOF_IN_STRUCT'

Почему

Тело класса открыто через `{` и не закрыто. Здесь имя токена на удивление полезно: оно означает, что файл закончился, пока мы всё ещё были внутри класса.

Решение

Закройте фигурную скобку на отдельной строке.

Ошибка
classDiagram
    class Заказ {
        +String номер
Исправлено
classDiagram
    class Заказ {
        +String номер
    }

Что вы видите

Parse error, заканчивается на: got 'ANNOTATION_END'

Почему

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

Решение

Используйте связь диаграммы классов: `-->` для ассоциации, `<|--` для наследования, `*--` для композиции.

Ошибка
classDiagram
    Заказ ->> Клиент
Исправлено
classDiagram
    Заказ --> Клиент : принадлежит

Что вы видите

No diagram type detected matching given configuration

Почему

Неверный регистр ключевого слова. `classdiagram` — это не `classDiagram`.

Решение

Сделайте D заглавной.

Ошибка
classdiagram
    class Заказ
Исправлено
classDiagram
    class Заказ

Что вы видите

Стрелка указывает в сторону, противоположную задуманной

Почему

Стрелки связей читаются от наконечника назад. `A <|-- B` означает, что B наследуется от A, а не наоборот. Написанная задом наперёд, она всё равно рисуется — просто теперь она утверждает, что базовый класс расширяет собственного наследника.

Решение

Читайте так: «дальний конец расширяет тот, куда указывает наконечник». Родителя ставьте слева от `<|--`.

Ошибка
classDiagram
    БанковскаяКарта <|-- СпособОплаты
Исправлено
classDiagram
    СпособОплаты <|-- БанковскаяКарта

Что вы видите

Появляется поле там, где вы ждали метод

Почему

Скобки — единственное, что отличает метод от поля. `+сохранить` — это поле с именем «сохранить»; `+сохранить()` — метод. Оба варианта допустимы, поэтому никакого предупреждения не будет.

Решение

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

Ошибка
classDiagram
    class Репо {
        +сохранить
        +найти
    }
Исправлено
classDiagram
    class Репо {
        +сохранить(Сущность с) void
        +найти(String id) Сущность
    }

Что вы видите

Композиция и агрегация выглядят почти одинаково, а означают противоположное

Почему

`*--` и `o--` отличаются одним символом и кодируют настоящую смысловую разницу: может ли часть пережить целое. Выбор не того варианта даёт диаграмму, формально безупречную и фактически неверную относительно вашей предметной области.

Решение

Закрашенный ромб `*--`, когда удаление родителя удаляет потомка. Полый `o--`, когда нет.

Ошибка
classDiagram
    Заказ o-- СтрокаЗаказа : содержит
Исправлено
classDiagram
    Заказ *-- СтрокаЗаказа : содержит

Заметки о рендеринге

Измерено на том Mermaid 11.12.2, который использует сайт.

Растёт в высоту быстрее любого другого типа здесь

Измерено на классах с двумя членами, связанных наследованием: три класса дают viewBox около 176×548, сорок классов — 180×7726, то есть примерно 194 пикселя высоты на класс, самый крутой рост среди шести типов сайта. Диаграмма на сорок классов переваливает за семь тысяч пикселей в высоту и как единая картинка непригодна. `direction LR` помогает, но после полутора десятков классов честное решение — разрезать диаграмму по границам подсистем.

Количество членов почти не влияет на ширину

Ширину задаёт самая длинная сигнатура одного члена, а не их количество. Класс с двадцатью короткими полями не шире класса с тремя. То есть: на члены можно не скупиться, а на классы — скупиться, что прямо противоположно интуиции. По-русски сигнатуры выходят длиннее английских, так что ширину обычно определяет один метод с подробным именем.

Кириллица работает и в именах классов, и внутри дженериков

Проверено: `Заказ`, `хэшПароля`, `БыстрыйПлатёж` годятся как имена классов и членов, и `Репозиторий~Пользователь~` тоже собирается без ошибок. Переводить модель на латиницу ради того, чтобы она нарисовалась, не нужно. Единственное настоящее ограничение задаёт тильда, которая является синтаксисом.

Дженерики используют тильды, и у этого есть следствие

`Репозиторий~T~` существует потому, что угловые скобки конфликтовали бы с HTML в подписях. Отсюда же следует, что буквальная тильда в имени класса или члена будет прочитана как начало параметра типа. Встречается редко, но сбивает с толку основательно.

Подписи — это HTML, поэтому экспорт в PNG перерисовывает

Подписи классов рисуются внутри `<foreignObject>` в SVG, который браузеры отказываются растрировать на canvas. Раньше экспорт в PNG на этом сайте молча давал сбой и возвращал SVG-файл; теперь он сначала перерисовывает диаграмму подписями обычным SVG-текстом. PNG получается правильным и в полном размере, с чуть иной типографикой по сравнению с экраном.

Когда лучше взять другую диаграмму

Если вы документируете базу данных, а не систему типов, берите ER-диаграмму. Различие содержательное: диаграммы классов моделируют поведение и наследование, которых у таблиц нет, а ER-диаграммы как следует моделируют ключи и кратность, которые диаграммы классов заминают.

Если на диаграмме в основном прямоугольники со стрелками `-->` и без членов, вы рисуете архитектуру, а не диаграмму классов. Блок-схема с подграфами будет выглядеть лучше и утверждать меньше.

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

Другие типы диаграмм

Автор Dominik Malsch · Обновлено:

Открыть редактор →