Za darmo · Bez rejestracji · Obsługuje pliki .mmd

Edytor diagramów klas Mermaid

Diagram klas pokazuje typy i ich wzajemne relacje: co co zawiera, co po czym dziedziczy, co od czego zależy. Nadaje się wtedy, gdy sednem jest kształt kodu — model dziedziny, interfejs rozszerzeń, drzewo dziedziczenia. Jeśli chcesz pokazać, co dzieje się w czasie działania, a nie jak typy do siebie pasują, użyj diagramu sekwencji.

Model dziedziny płatności

Trzy rodzaje relacji na jednym diagramie: kompozycja dla części, które nie przeżywają całości, dziedziczenie dla hierarchii metod płatności i zwykła asocjacja z licznością. Znaczenie niosą tu przede wszystkim strzałki; prostokąty klas są niemal dodatkiem.

classDiagram
    class Zamówienie {
        +String numer
        +StatusZamówienia status
        +Kwota suma()
        +void dodajPozycję(Produkt p, int ilość)
    }
    class PozycjaZamówienia {
        +Produkt produkt
        +int ilość
        +Kwota wartość()
    }
    class MetodaPłatności {
        <<abstract>>
        +autoryzuj(Kwota kwota) bool
    }
    class KartaPłatnicza {
        +String ostatnie4
        +autoryzuj(Kwota kwota) bool
    }
    class PrzelewBankowy {
        +String iban
        +autoryzuj(Kwota kwota) bool
    }
    class BLIK {
        +String kodJednorazowy
        +autoryzuj(Kwota kwota) bool
    }

    Zamówienie "1" *-- "1..*" PozycjaZamówienia : zawiera
    Zamówienie --> MetodaPłatności : opłacane przez
    MetodaPłatności <|-- KartaPłatnicza
    MetodaPłatności <|-- PrzelewBankowy
    MetodaPłatności <|-- BLIK
Otwórz to w edytorze
Reklama

Omówione przykłady

1. Jedna klasa

`+` to publiczny, `-` prywatny, `#` chroniony. Składnik z nawiasami rysowany jest jako metoda; bez nich jest polem.

classDiagram
    class Użytkownik {
        +String email
        -String hasłoHash
        +bool zweryfikuj(String kandydat)
    }
Otwórz w edytorze

2. Dziedziczenie i interfejsy

`<|--` to dziedziczenie, czytane jako «ten po prawej rozszerza tego po lewej». Adnotacja `<<interface>>` jest etykietą, a nie zachowaniem, ale to ona decyduje o czytelności diagramu.

classDiagram
    class Repozytorium {
        <<interface>>
        +znajdź(String id) Encja
        +zapisz(Encja e) void
    }
    class RepozytoriumPostgres {
        -Połączenie połączenie
        +znajdź(String id) Encja
        +zapisz(Encja e) void
    }
    class RepozytoriumWPamięci {
        -Map magazyn
        +znajdź(String id) Encja
        +zapisz(Encja e) void
    }
    Repozytorium <|.. RepozytoriumPostgres
    Repozytorium <|.. RepozytoriumWPamięci
Otwórz w edytorze

3. Kompozycja kontra agregacja

Różnica dotyczy czasu życia. Wypełniony romb (`*--`) znaczy, że część ginie razem z całością: usuń fakturę, a jej pozycje przepadną. Pusty romb (`o--`) znaczy, że część istnieje niezależnie.

classDiagram
    class Faktura {
        +String numer
    }
    class PozycjaFaktury {
        +String nazwa
    }
    class Kontrahent {
        +String nazwa
    }
    Faktura "1" *-- "1..*" PozycjaFaktury : składa się z
    Kontrahent "1" o-- "0..*" Faktura : wystawił
Otwórz w edytorze

4. Typy generyczne

Tyldy dają parametry typu: `Repozytorium~Użytkownik~`. Zagnieżdżanie też działa, co bywa konieczne i rzadko jest dobrym pomysłem. Polskie znaki działają wewnątrz typu generycznego bez przeszkód.

classDiagram
    class Repozytorium~T~ {
        +znajdź(String id) T
        +wszystkie() List~T~
    }
    class Cache~K, V~ {
        +pobierz(K klucz) V
        +włóż(K klucz, V wartość) void
    }
    class RepozytoriumUżytkowników {
        +znajdźPoEmailu(String email) Użytkownik
    }
    Repozytorium~Użytkownik~ <|-- RepozytoriumUżytkowników
Otwórz w edytorze

5. Notatki i kierunek

`direction LR` układa diagram z lewej do prawej, co drzewu dziedziczenia zwykle służy lepiej niż układ domyślny. Notatka to właściwe miejsce na ograniczenie, które nie mieści się w prostokącie klasy.

classDiagram
    direction LR
    class MagazynZdarzeń {
        +dopisz(Zdarzenie z) void
        +odtwórz(String strumień) List~Zdarzenie~
    }
    class Migawka {
        +int wersja
        +byte[] zawartość
    }
    MagazynZdarzeń --> Migawka : zapisuje co 100 zdarzeń
    note for MagazynZdarzeń "Tylko dopisywanie. Zdarzenia nigdy nie są zmieniane ani usuwane."
Otwórz w edytorze

Ściąga ze składni diagramu klas

Warto zapamiętać strzałki relacji: to one odróżniają diagram klas od rysunku prostokątów i linii, i czyta się je od prawej do lewej, co długo wprowadza w błąd.

SkładniaZnaczenie
classDiagramOtwiera diagram. Rozróżnia wielkość liter.
class Nazwa { ... }Klasa ze składnikami. Klamra zamykająca w osobnym wierszu.
+składnikPubliczny.
-składnikPrywatny.
#składnikChroniony.
+metoda(Typ arg) TypZwracanyMetoda — czynią ją nią właśnie nawiasy.
<<interface>> / <<abstract>>Stereotyp, zapisany w pierwszym wierszu wewnątrz klasy.
A <|-- BDziedziczenie: B rozszerza A.
A <|.. BRealizacja: B implementuje interfejs A.
A *-- BKompozycja: B nie przeżywa A.
A o-- BAgregacja: B może istnieć bez A.
A --> BAsocjacja z kierunkiem.
A ..> BZależność — A używa B, ale go nie przechowuje.
A "1" --> "0..*" B : etykietaLiczność na obu końcach plus etykieta relacji.
class Repo~T~Parametr typu generycznego.
note for A "tekst"Notatka dołączona do klasy.
direction LRZmienia kierunek układu.
Reklama

Błędy, które psują diagram klas

Odtworzone na Mermaidzie 11.12.2. Diagram klas należy do bardziej wyrozumiałych spośród sześciu typów, więc połowa z tych błędów rysuje się spokojnie i zwraca niewłaściwy obraz.

Co widzisz

Parse error, kończy się na: got 'EOF_IN_STRUCT'

Dlaczego

Ciało klasy otwarte przez `{` i nigdy niezamknięte. Tym razem nazwa tokenu naprawdę pomaga: oznacza, że plik skończył się, gdy wciąż byliśmy wewnątrz klasy.

Rozwiązanie

Zamknij klamrę w osobnym wierszu.

Błędnie
classDiagram
    class Zamówienie {
        +String numer
Poprawnie
classDiagram
    class Zamówienie {
        +String numer
    }

Co widzisz

Parse error, kończy się na: got 'ANNOTATION_END'

Dlaczego

Strzałka z diagramu sekwencji użyta w diagramie klas. `->>` nic tu nie znaczy, a parser wchodzi w nią na tyle głęboko, że zwraca mylącą nazwę tokenu.

Rozwiązanie

Użyj relacji z diagramu klas: `-->` dla asocjacji, `<|--` dla dziedziczenia, `*--` dla kompozycji.

Błędnie
classDiagram
    Zamówienie ->> Klient
Poprawnie
classDiagram
    Zamówienie --> Klient : należy do

Co widzisz

No diagram type detected matching given configuration

Dlaczego

Zła wielkość liter w słowie kluczowym. `classdiagram` to nie `classDiagram`.

Rozwiązanie

Wielkie D.

Błędnie
classdiagram
    class Zamówienie
Poprawnie
classDiagram
    class Zamówienie

Co widzisz

Strzałka wskazuje w przeciwną stronę, niż chciałeś

Dlaczego

Strzałki relacji czyta się od grota wstecz. `A <|-- B` znaczy, że B dziedziczy po A, a nie odwrotnie. Napisana odwrotnie i tak się narysuje — tyle że teraz twierdzi, iż klasa bazowa rozszerza własną podklasę.

Rozwiązanie

Czytaj to jako «dalszy koniec rozszerza koniec z grotem». Rodzica postaw po lewej stronie `<|--`.

Błędnie
classDiagram
    KartaPłatnicza <|-- MetodaPłatności
Poprawnie
classDiagram
    MetodaPłatności <|-- KartaPłatnicza

Co widzisz

Pojawia się pole tam, gdzie spodziewałeś się metody

Dlaczego

Nawiasy to jedyne, co odróżnia metodę od pola. `+zapisz` to pole o nazwie zapisz; `+zapisz()` to metoda. Obie formy są poprawne, więc nic nie ostrzega.

Rozwiązanie

Dodaj nawiasy, a za nimi typ zwracany, jeśli chcesz go widzieć.

Błędnie
classDiagram
    class Repo {
        +zapisz
        +znajdź
    }
Poprawnie
classDiagram
    class Repo {
        +zapisz(Encja e) void
        +znajdź(String id) Encja
    }

Co widzisz

Kompozycja i agregacja wyglądają na pierwszy rzut oka tak samo, a znaczą coś przeciwnego

Dlaczego

`*--` i `o--` różnią się jednym znakiem, a kodują realną różnicę znaczeniową: czy część może przeżyć całość. Użycie niewłaściwego daje diagram formalnie poprawny i rzeczowo fałszywy wobec twojej dziedziny.

Rozwiązanie

Wypełniony romb `*--`, gdy usunięcie rodzica usuwa dziecko. Pusty `o--`, gdy nie usuwa.

Błędnie
classDiagram
    Zamówienie o-- PozycjaZamówienia : zawiera
Poprawnie
classDiagram
    Zamówienie *-- PozycjaZamówienia : zawiera

Uwagi o renderowaniu

Zmierzone na Mermaidzie 11.12.2, którego używa ta strona.

Rośnie w górę szybciej niż jakikolwiek inny typ tutaj

Zmierzone na klasach po dwa składniki, spiętych dziedziczeniem: trzy klasy dają viewBox około 176×548, a czterdzieści 180×7726, czyli mniej więcej 194 piksele wysokości na klasę — najbardziej stromy przyrost spośród sześciu typów na tej stronie. Diagram czterdziestu klas przekracza siedem tysięcy pikseli wysokości i jako jeden obrazek jest bezużyteczny. `direction LR` pomaga, ale powyżej mniej więcej piętnastu klas uczciwym rozwiązaniem jest podzielenie diagramu według granic dziedzin.

Liczba składników prawie nie wpływa na szerokość

Szerokość dyktuje najdłuższa sygnatura pojedynczego składnika, a nie to, ilu ich jest. Klasa z dwudziestoma krótkimi polami nie jest szersza niż ta z trzema. Innymi słowy: składnikom można być hojnym, a klasom skąpym, co jest dokładną odwrotnością odruchu. Po polsku sygnatury wychodzą dłuższe niż po angielsku, więc o szerokości decyduje zwykle jedna metoda o opisowej nazwie.

Polskie znaki działają wszędzie, także w typach generycznych

Sprawdzone: `Zamówienie`, `hasłoHash` i `PozycjaZamówienia` działają jako nazwy klas i składników, a `Repozytorium~Użytkownik~` również składa się bez błędu. Nie trzeba pozbawiać modelu ogonków, żeby się narysował. Jedyne rzeczywiste ograniczenie wprowadza tylda, która jest składnią.

Typy generyczne używają tyld i ma to konsekwencję

`Repozytorium~T~` wygląda tak, bo nawiasy ostre kolidowałyby z HTML-em w etykietach. Wynika z tego również, że dosłowna tylda w nazwie klasy albo składnika zostanie odczytana jako początek parametru typu. Rzadkie, ale gdy się zdarzy, potrafi solidnie zdezorientować.

Etykiety to HTML, więc eksport do PNG przerysowuje

Etykiety klas rysowane są wewnątrz `<foreignObject>` w SVG, a przeglądarki odmawiają rasteryzowania tego na canvasie. Eksport do PNG na tej stronie wcześniej po cichu zawodził i oddawał plik SVG; teraz najpierw przerysowuje diagram etykietami z czystego tekstu SVG. PNG wychodzi poprawny i w pełnym rozmiarze, ze składem odrobinę innym niż na ekranie.

Kiedy lepszy będzie inny diagram

Jeśli dokumentujesz bazę danych, a nie system typów, użyj diagramu ER. Rozróżnienie ma znaczenie: diagramy klas modelują zachowanie i dziedziczenie, których tabele nie mają, a diagramy ER porządnie modelują klucze i liczności, co diagramy klas zbywają po łebkach.

Jeśli diagram to głównie prostokąty połączone `-->` i bez składników, rysujesz architekturę, a nie diagram klas. Schemat blokowy z podgrafami będzie wyglądał lepiej i będzie twierdził mniej.

A jeśli lista klas powstaje z kodu, zastanów się, czy diagram też nie powinien. Diagram klas utrzymywany ręcznie dla bazy kodu zmieniającej się co tydzień staje się nieprawdziwy w miesiąc, a błędny diagram kosztuje więcej niż brak diagramu.

Inne typy diagramów

Autor Dominik Malsch · Ostatnia aktualizacja:

Otwórz edytor →