Gratis · Geen registratie · Werkt met .mmd-bestanden

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 <|-- Incassomachtiging
Open dit in de editor
Advertentie

Uitgewerkte 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)
    }
In de editor openen

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 <|.. GeheugenRepository
In de editor openen

3. 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 ontvangen
In de editor openen

4. 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~ <|-- Gebruikersrepository
In de editor openen

5. 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."
In de editor openen

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.

SyntaxisBetekenis
classDiagramOpent het diagram. Hoofdlettergevoelig.
class Naam { ... }Klasse met leden. De sluitende accolade op een eigen regel.
+lidPubliek.
-lidPrivé.
#lidBeschermd.
+methode(Type arg) RetourtypeMethode — de haakjes maken het er een.
<<interface>> / <<abstract>>Stereotype, op de eerste regel binnen de klasse.
A <|-- BOvererving: B breidt A uit.
A <|.. BRealisatie: B implementeert interface A.
A *-- BCompositie: B overleeft A niet.
A o-- BAggregatie: B kan zonder A bestaan.
A --> BAssociatie met richting.
A ..> BAfhankelijkheid — A gebruikt B maar bewaart het niet.
A "1" --> "0..*" B : labelMultipliciteit aan beide kanten plus het relatielabel.
class Repo~T~Generieke typeparameter.
note for A "tekst"Notitie gekoppeld aan een klasse.
direction LRVerandert de richting van de indeling.
Advertentie

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.

Fout
classDiagram
    class Bestelling {
        +String nummer
Goed
classDiagram
    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.

Fout
classDiagram
    Bestelling ->> Klant
Goed
classDiagram
    Bestelling --> Klant : hoort bij

Wat je ziet

No diagram type detected matching given configuration

Waarom

Verkeerde hoofdletters in het sleutelwoord. `classdiagram` is niet `classDiagram`.

Oplossing

Hoofdletter D.

Fout
classdiagram
    class Bestelling
Goed
classDiagram
    class Bestelling

Wat 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 `<|--`.

Fout
classDiagram
    Ideal <|-- Betaalwijze
Goed
classDiagram
    Betaalwijze <|-- Ideal

Wat 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.

Fout
classDiagram
    class Repo {
        +bewaar
        +zoek
    }
Goed
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.

Fout
classDiagram
    Bestelling o-- Bestelregel : bevat
Goed
classDiagram
    Bestelling *-- Bestelregel : bevat

Aantekeningen 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:

Editor openen →