Mermaid klassendiagram-editor
Een klassendiagram laat typen zien en hoe ze zich tot elkaar verhouden: wat wat bevat, wat van wat overerft, wat van wat afhangt. Het past wanneer de vorm van de code de kern is — een domeinmodel, een uitbreidingsinterface, een overervingsboom. Wil je tonen wat er tijdens de uitvoering gebeurt in plaats van hoe de typen in elkaar passen, gebruik dan een sequentiediagram.
Domeinmodel van het betalen
Drie soorten relaties in één diagram: compositie voor onderdelen die het geheel niet overleven, overerving voor de hiërarchie van betaalwijzen en een gewone associatie met multipliciteit. De betekenis zit vooral in de pijlen; de klassenrechthoeken zijn bijna bijzaak.
classDiagram
class Bestelling {
+String nummer
+Bestelstatus status
+Bedrag totaal()
+void voegRegelToe(Product p, int aantal)
}
class Bestelregel {
+Product product
+int aantal
+Bedrag waarde()
}
class Betaalwijze {
<<abstract>>
+autoriseer(Bedrag bedrag) bool
}
class Ideal {
+String bankcode
+autoriseer(Bedrag bedrag) bool
}
class Creditcard {
+String laatste4
+autoriseer(Bedrag bedrag) bool
}
class Incassomachtiging {
+String iban
+autoriseer(Bedrag bedrag) bool
}
Bestelling "1" *-- "1..*" Bestelregel : bevat
Bestelling --> Betaalwijze : betaald met
Betaalwijze <|-- Ideal
Betaalwijze <|-- Creditcard
Betaalwijze <|-- IncassomachtigingUitgewerkte voorbeelden
1. Eén klasse
`+` is publiek, `-` privé, `#` beschermd. Een lid met haakjes wordt als methode getekend; zonder haakjes is het een veld.
classDiagram
class Gebruiker {
+String email
-String wachtwoordhash
+bool controleer(String kandidaat)
}2. Overerving en interfaces
`<|--` is overerving, te lezen als «die rechts breidt die links uit». De annotatie `<<interface>>` is een etiket en geen gedrag, maar bepaalt wel de leesbaarheid van het diagram.
classDiagram
class Repository {
<<interface>>
+zoek(String id) Entiteit
+bewaar(Entiteit e) void
}
class PostgresRepository {
-Verbinding verbinding
+zoek(String id) Entiteit
+bewaar(Entiteit e) void
}
class GeheugenRepository {
-Map opslag
+zoek(String id) Entiteit
+bewaar(Entiteit e) void
}
Repository <|.. PostgresRepository
Repository <|.. GeheugenRepository3. Compositie tegenover aggregatie
Het verschil gaat over levensduur. De gevulde ruit (`*--`) betekent dat het onderdeel met het geheel verdwijnt: verwijder de factuur en de regels zijn weg. De open ruit (`o--`) betekent dat het onderdeel zelfstandig bestaat.
classDiagram
class Factuur {
+String nummer
}
class Factuurregel {
+String omschrijving
}
class Debiteur {
+String bedrijfsnaam
}
Factuur "1" *-- "1..*" Factuurregel : bestaat uit
Debiteur "1" o-- "0..*" Factuur : heeft ontvangen4. Generieke typen
Tildes geven typeparameters: `Repository~Gebruiker~`. Nesten werkt ook, wat soms nodig is en zelden een goed idee. Nederlandse samenstellingen werken zonder bezwaar binnen een generiek type.
classDiagram
class Repository~T~ {
+zoek(String id) T
+alle() List~T~
}
class Cache~K, V~ {
+haal(K sleutel) V
+zet(K sleutel, V waarde) void
}
class Gebruikersrepository {
+zoekOpEmail(String email) Gebruiker
}
Repository~Gebruiker~ <|-- Gebruikersrepository5. Notities en richting
`direction LR` legt het diagram van links naar rechts, wat een overervingsboom meestal beter past dan de standaardindeling. De notitie is de juiste plek voor de beperking die niet in de klassenrechthoek past.
classDiagram
direction LR
class Gebeurtenisopslag {
+voegToe(Gebeurtenis g) void
+speelAf(String stroom) List~Gebeurtenis~
}
class Momentopname {
+int versie
+byte[] inhoud
}
Gebeurtenisopslag --> Momentopname : schrijft elke 100 gebeurtenissen
note for Gebeurtenisopslag "Alleen toevoegen. Gebeurtenissen worden nooit gewijzigd of verwijderd."Overzicht van de klassendiagram-syntaxis
De relatiepijlen zijn het onthouden waard: ze onderscheiden een klassendiagram van een tekening met rechthoeken en lijnen, en je leest ze van rechts naar links, wat nog lang blijft verwarren.
| Syntaxis | Betekenis |
|---|---|
| classDiagram | Opent het diagram. Hoofdlettergevoelig. |
| class Naam { ... } | Klasse met leden. De sluitende accolade op een eigen regel. |
| +lid | Publiek. |
| -lid | Privé. |
| #lid | Beschermd. |
| +methode(Type arg) Retourtype | Methode — de haakjes maken het er een. |
| <<interface>> / <<abstract>> | Stereotype, op de eerste regel binnen de klasse. |
| A <|-- B | Overerving: B breidt A uit. |
| A <|.. B | Realisatie: B implementeert interface A. |
| A *-- B | Compositie: B overleeft A niet. |
| A o-- B | Aggregatie: B kan zonder A bestaan. |
| A --> B | Associatie met richting. |
| A ..> B | Afhankelijkheid — A gebruikt B maar bewaart het niet. |
| A "1" --> "0..*" B : label | Multipliciteit aan beide kanten plus het relatielabel. |
| class Repo~T~ | Generieke typeparameter. |
| note for A "tekst" | Notitie gekoppeld aan een klasse. |
| direction LR | Verandert de richting van de indeling. |
De fouten die een klassendiagram slopen
Nagemaakt op Mermaid 11.12.2. Het klassendiagram hoort tot de soepelere van de zes typen, dus de helft van deze fouten tekent gewoon door en geeft je het verkeerde plaatje.
Wat je ziet
Parse error die eindigt op: got 'EOF_IN_STRUCT'
Waarom
Een klassenlichaam dat met `{` geopend is en nooit gesloten. Deze keer helpt de tokennaam echt: hij zegt dat het bestand ophield terwijl we nog binnen een klasse zaten.
Oplossing
Sluit de accolade op een eigen regel.
classDiagram
class Bestelling {
+String nummerclassDiagram
class Bestelling {
+String nummer
}Wat je ziet
Parse error die eindigt op: got 'ANNOTATION_END'
Waarom
Een pijl uit het sequentiediagram gebruikt in een klassendiagram. `->>` betekent hier niets, en de parser gaat er ver genoeg in mee om een misleidende tokennaam terug te geven.
Oplossing
Gebruik de relaties van het klassendiagram: `-->` voor associatie, `<|--` voor overerving, `*--` voor compositie.
classDiagram
Bestelling ->> KlantclassDiagram
Bestelling --> Klant : hoort bijWat je ziet
No diagram type detected matching given configuration
Waarom
Verkeerde hoofdletters in het sleutelwoord. `classdiagram` is niet `classDiagram`.
Oplossing
Hoofdletter D.
classdiagram
class BestellingclassDiagram
class BestellingWat je ziet
De pijl wijst de andere kant op dan je bedoelde
Waarom
Relatiepijlen lees je vanaf de punt terug. `A <|-- B` betekent dat B van A overerft, niet andersom. Omgekeerd geschreven tekent het gewoon — alleen beweert het nu dat de basisklasse zijn eigen subklasse uitbreidt.
Oplossing
Lees het als «het verre uiteinde breidt het uiteinde met de punt uit». Zet de ouder links van `<|--`.
classDiagram
Ideal <|-- BetaalwijzeclassDiagram
Betaalwijze <|-- IdealWat je ziet
Er verschijnt een veld waar je een methode verwachtte
Waarom
De haakjes zijn het enige dat een methode van een veld onderscheidt. `+bewaar` is een veld dat bewaar heet; `+bewaar()` is een methode. Beide vormen zijn geldig, dus niets waarschuwt.
Oplossing
Voeg de haakjes toe, en daarna het retourtype als je dat wilt zien.
classDiagram
class Repo {
+bewaar
+zoek
}classDiagram
class Repo {
+bewaar(Entiteit e) void
+zoek(String id) Entiteit
}Wat je ziet
Compositie en aggregatie lijken op het eerste gezicht hetzelfde en zeggen het omgekeerde
Waarom
`*--` en `o--` schelen één teken en coderen een echt betekenisverschil: of het onderdeel het geheel kan overleven. De verkeerde gebruiken levert een diagram op dat formeel klopt en inhoudelijk onwaar is over jouw domein.
Oplossing
Gevulde ruit `*--` wanneer de ouder verwijderen het kind verwijdert. Open `o--` wanneer dat niet zo is.
classDiagram
Bestelling o-- Bestelregel : bevatclassDiagram
Bestelling *-- Bestelregel : bevatAantekeningen over het tekenen
Gemeten op Mermaid 11.12.2, de versie die deze site gebruikt.
Het groeit sneller omhoog dan elk ander type hier
Gemeten op klassen met twee leden elk, aan elkaar geknoopt met overerving: drie klassen geven een viewBox van ongeveer 176×548, veertig geven er 180×7726, ofwel ruwweg 194 pixels hoogte per klasse — de steilste groei van de zes typen op deze site. Een diagram van veertig klassen gaat over de zevenduizend pixels hoogte en is als één afbeelding onbruikbaar. `direction LR` helpt, maar boven een stuk of vijftien klassen is de eerlijke oplossing het diagram langs domeingrenzen opsplitsen.
Het aantal leden beïnvloedt de breedte nauwelijks
De breedte wordt bepaald door de langste handtekening van één lid, niet door hoeveel er zijn. Een klasse met twintig korte velden is niet breder dan een met drie. Anders gezegd: wees royaal met leden en zuinig met klassen, wat precies het omgekeerde is van het instinct. In het Nederlands is dit extra scherp, want één samengestelde methodenaam als `verwerkBetalingsbevestiging` bepaalt in zijn eentje de breedte van de hele klasse.
Samenstellingen werken overal, ook als klassennaam
Getoetst: `Betalingsbevestiging`, `Incassomachtiging` en `Gebruikersrepository` werken als klassennaam en als lidnaam, en `Repository~Gebruiker~` zet ook zonder fout. Er is dus geen reden om Nederlandse termen op te breken om ze getekend te krijgen. De enige echte beperking is de tilde, want die is syntaxis — en de spatie, die er in een aaneengeschreven samenstelling toch al niet hoort.
Generieke typen gebruiken tildes en dat heeft een gevolg
`Repository~T~` ziet er zo uit omdat punthaken zouden botsen met de HTML in labels. Daaruit volgt ook dat een letterlijke tilde in een klassen- of lidnaam gelezen wordt als het begin van een typeparameter. Zeldzaam, maar wanneer het gebeurt zet het je flink op het verkeerde been.
Labels zijn HTML, dus de PNG-export tekent opnieuw
Klassenlabels worden binnen een `<foreignObject>` in de SVG getekend, en browsers weigeren dat op een canvas te rasteren. De PNG-export van deze site faalde vroeger stilzwijgend en gaf een SVG-bestand terug; nu wordt het diagram eerst opnieuw getekend met gewone SVG-tekstlabels. De PNG komt correct en op volle grootte uit, met een zetwijze die iets afwijkt van het scherm.
Wanneer een ander diagram beter past
Documenteer je een database en geen typesysteem, gebruik dan een ER-diagram. Het onderscheid telt: klassendiagrammen modelleren gedrag en overerving, die tabellen niet hebben, en ER-diagrammen modelleren sleutels en multipliciteiten netjes, waar klassendiagrammen luchtig overheen gaan.
Bestaat het diagram vooral uit rechthoeken verbonden met `-->` en zonder leden, dan teken je een architectuur en geen klassendiagram. Een stroomdiagram met subgrafieken ziet er beter uit en beweert minder.
En komt de lijst met klassen uit de code, vraag je dan af of het diagram dat ook niet zou moeten. Een handmatig bijgehouden klassendiagram van een codebase die wekelijks verandert, is binnen een maand onwaar, en een verkeerd diagram kost meer dan geen diagram.
Andere diagramtypen
Geschreven door Dominik Malsch · Laatst bijgewerkt: