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 <|-- BLIKOmó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)
}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ęci3. 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ł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ów5. 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."Ś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ładnia | Znaczenie |
|---|---|
| classDiagram | Otwiera diagram. Rozróżnia wielkość liter. |
| class Nazwa { ... } | Klasa ze składnikami. Klamra zamykająca w osobnym wierszu. |
| +składnik | Publiczny. |
| -składnik | Prywatny. |
| #składnik | Chroniony. |
| +metoda(Typ arg) TypZwracany | Metoda — czynią ją nią właśnie nawiasy. |
| <<interface>> / <<abstract>> | Stereotyp, zapisany w pierwszym wierszu wewnątrz klasy. |
| A <|-- B | Dziedziczenie: B rozszerza A. |
| A <|.. B | Realizacja: B implementuje interfejs A. |
| A *-- B | Kompozycja: B nie przeżywa A. |
| A o-- B | Agregacja: B może istnieć bez A. |
| A --> B | Asocjacja z kierunkiem. |
| A ..> B | Zależność — A używa B, ale go nie przechowuje. |
| A "1" --> "0..*" B : etykieta | Liczność na obu końcach plus etykieta relacji. |
| class Repo~T~ | Parametr typu generycznego. |
| note for A "tekst" | Notatka dołączona do klasy. |
| direction LR | Zmienia kierunek układu. |
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.
classDiagram
class Zamówienie {
+String numerclassDiagram
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.
classDiagram
Zamówienie ->> KlientclassDiagram
Zamówienie --> Klient : należy doCo 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.
classdiagram
class ZamówienieclassDiagram
class ZamówienieCo 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 `<|--`.
classDiagram
KartaPłatnicza <|-- MetodaPłatnościclassDiagram
MetodaPłatności <|-- KartaPłatniczaCo 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ć.
classDiagram
class Repo {
+zapisz
+znajdź
}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.
classDiagram
Zamówienie o-- PozycjaZamówienia : zawieraclassDiagram
Zamówienie *-- PozycjaZamówienia : zawieraUwagi 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: