Klassendiagram

Ontwerp

Een klassendiagram is een schematische weergave van de structuur van een informatiesysteem dat opgedeeld is in klassen. Meestal met de bedoeling dat dit informatiesysteem geïmplementeerd wordt in een objectgeoriënteerde programmeertaal. Waarbij een klasse een geëncapsuleerd subsysteem is dat data omvat en alle operaties die met deze data werken.

Van het klassendiagram bestaan 3 verschijningsvormen te weten Domeinmodel, Applicatiemodel en Implementatiemodel. In dit artikel wanneer ik spreek over een klassendiagram bedoel ik altijd het Applicatiemodel. In plaats van het domeinmodel gebruik ik zelf liever het conceptueel model omdat dit een notatiewijze heeft die gemakkelijker te begrijpen is voor de opdrachtgever en verder hetzelfde doel bereikt als het domeinmodel. Bij het implementatiemodel twijfel ik aan de meerwaarde in de onderwerpfase omdat er tijdens de implementatiefase toch nog wijzigingen komen die vooraf niet voorzien waren. Als het project om een implementatiemodel vraagt dan is dat meestal om te documenteren hoe het uiteindelijk geïmplementeerd is (dus niet vooraf) en een dergelijk model kan dan met een tool gegenereerd worden uit de code.

Het doel van het klassendiagram is om eenduidig weer te geven hoe de structuur van het informatiesysteem geïmplementeerd dient te worden. Het is sterk af te raden om in de implementatiefase af te wijken van de genomen beslissingen in de ontwerpfase omdat dit het samenwerken met collega's moeilijker maakt. Daarom is het verstandig om in de ontwerpfase, en dus in het klassendiagram, uitsluitend die ontwerpbeslissingen te nemen waar men erg zeker van is dat ze correct zijn. Detailbeslissingen kunnen beter uitgesteld worden tot in de implementatiefase.

Notatie

Een klassendiagram wordt meestal geschreven in een gestandaardiseerde taal voor modelleren met de naam UML. Helaas wordt er in het bedrijfsleven niet altijd heel nauwkeurig met de notatiewijze van het UML klassendiagram omgegaan waardoor er inmiddels veel "dialecten" en eigen, interne varianten bestaan. Natuurlijk is het niet direct een probleem als er in een groep collega's een variant leeft die door allen omarmd en begrepen wordt, het nadeel zit hem in het feit dat iemand die niet bij deze interne groep hoort het diagram niet (goed) kan lezen en het daarom wellicht verkeerd implementeert. Hierdoor is het diagram moeilijker overdraagbaar en kost het extra inwerktijd voor een nieuwe collega. Het is daarom aan te raden om je altijd zo goed mogelijk aan de standaardtaal te houden om zo onduidelijkheid te voorkomen.

Klassen

Het klassendiagram is een ontwerp voor een objectgeoriënteerde implementatie en gaat dus uit van het feit dat het informatiesysteem objecten bevat welke van een type zijn dat door een klasse gedefinieerd wordt. Het klassendiagram geeft aan hoe deze klassen gemaakt dienen te worden en hoe ze met elkaar samenwerken. Een klasse in het klassendiagram is een rechthoek met 3 zones, waarvan de bovenste zone de naam van de klasse bevat, de middelste de attributen van de klasse en de onderste zone de operaties die deze klasse kan uitvoeren. De naam definieert wat de klasse is, de attributen zijn de dingen die een klasse heeft, en operaties zijn de acties die een klasse kan.

full fig.1: Links het sjabloon van een klasse, rechts een voorbeeld van een klasse Auto.

Attributen hebben altijd een type. Operaties retourneren soms een object en soms niet. Wanneer de operatie een object retourneert dan staat het type van dat object achter de operatie, maar wanneer de operatie geen object retourneert dan staat er niets. Hetzelfde geldt voor de parameters van de operatie. Heeft de operatie parameters dan hebben deze een naam en een type, echter wanneer de operatie geen parameters heeft, dan staat er niets.

Een paar handige vuistregels voor de notatie van het klassendiagram:

  • Alle namen beginnen met een kleine letter, behalve de types.
  • Het type staat altijd achter de naam van een attribuut of parameter.
  • Types worden altijd voluit geschreven, dit zijn de namen van je eigen klassen maar ook de primitieve types: String, Integer, Boolean, Real.1
  • Operaties hebben altijd twee haakjes achter de naam, ook als er geen parameters zijn.

Een type kan ook meerdere objecten van hetzelfde type zijn, we spreken dan over een collectie van het type. Hieronder een voorbeeld van een persoon die meerdere lievelingskleuren kan hebben. Deze worden opgeslagen in een collectie van String, genoteerd als String[].

full fig.2: Een persoon heeft 1 naam, 1 geboortejaar en potentieel meerdere lievelingskleuren.

Associaties

Omdat klassen ook types zijn, is het uiteraard mogelijk een attribuut te maken van een type dat gedefinieerd wordt door een eigen gemaakte klasse. Bijvoorbeeld: Een Boek zou een attribuut kunnen hebben met de naam 'auteur' van het type Persoon, waarbij Persoon ook een klasse is in ons eigen ontwerp. Je zou kunnen aannemen dat dit zo gemodelleerd dient te worden als hieronder weergegeven, maar helaas is dit incorrect.

full fig.3: Een boek met een attribuut dat een eigen gemaakte klasse is (auteur : Persoon). Helaas verkeerd gemodelleerd.

Omdat Persoon ook een klasse is die we zelf bedacht hebben, is deze uiteraard aanwezig in het klassendiagram. Hierdoor kunnen we zeggen dat deze 2 klassen geassocieerd zijn, ze hebben "iets" met elkaar. De juiste manier om dit te modelleren wordt hieronder weergegeven.

full fig.4: Een boek met een attribuut dat een eigen gemaakte klasse is (auteur : Persoon). Correct gemodelleerd.

De pijl geeft aan dat Boek een attribuut heeft met de naam auteur (staat bij de pijlpunt) en dit attribuut is van het type Persoon, omdat de pijlpunt vast zit aan de klasse Persoon. De 1 bij de pijlpunt geeft aan dat Boek altijd precies 1 auteur heeft. Het sterretje bij de voet van de pijl geeft aan dat een Persoon geassocieerd kan zijn met meerdere boeken. Een auteur kan blijkbaar meerdere boeken schrijven. Hieronder nog een voorbeeld van een Vijver met meerdere aanwezige vissen, waarin een Vijver meerdere vissen heeft, maar een Vis maar met 1 vijver geassocieerd is.

full fig.5: Een klassendiagram met een klasse Vijver die een attribuut heeft met de naam aanwezigeVissen met daarin een collectie van Vis, potentieel meerdere vissen dus.

Het is ook mogelijk om een "normale" associatie te verrijken met een symbool voor aggregatie of compositie. Je herkent dit aan het gebruik van een zwarte of witte diamant aan 1 kant van de associatie. In bijzonder zeldzame gevallen kun je hiermee je ontwerp verduidelijken, echter in de meeste programmeertalen heeft deze toevoeging nauwelijks impact bij de implementatie. Ik zou daarom adviseren om erg spaarzaam om te gaan met deze symbolen en ze alleen toe te voegen wanneer de normale associatie (doorgetrokken streep met pijlpunt) niet afdoende is om het idee duidelijk te maken. In zo'n situatie zou ik zelf liever een tekstuele uitleg geven in plaatst van het gebruiken van een zeldzaam symbool, wat door de collega die mijn ontwerp gaat implementeren misschien verkeerd geïnterpreteerd kan worden.

Van concept naar ontwerp

Nadat het conceptueel model besproken is met de opdrachtgever en dus aangenomen kan worden dat dit correct is, kan het concept omgezet worden tot een ontwerp.

Als voorbeeld neem ik hier het conceptuele model van een eenvoudige webwinkel uit het artikel over het conceptueel model. Mocht je niet meer helemaal weten hoe het ook al weer zat, kijk dan nog even terug in dat artikel.

full fig.6: Een conceptueel model voor een eenvoudige webwinkel.

Het conceptueel model laat alleen zien welke concepten er in het domein bestaan en hoe deze concepten met elkaar samenwerken. We kunnen dit direct omzetten naar een klassendiagram waarbij een concept een klasse wordt.

full fig.7: Het (begin van een) klassendiagram gemaakt van bovenstaand conceptueel model.

De associaties blijven uiteraard zoals in het conceptueel model, maar in het klassendiagram krijgt elke pijlpunt een rolnaam. Mocht er in het conceptueel model al een rolnaam staan, dan wordt deze overgenomen. Heeft de associatie een multipliciteit van 1, dan is de rolnaam enkelvoud, bij meer dan 1 is het meervoud. Tevens wordt bij de voet van de pijl aangeven hoe de associatie logisch gezien werkt. Bijvoorbeeld: Een instantie van Klant heeft een associatie met (potentieel) meerdere instanties van Bestelling (ster bij pijlpunt) en deze noemen we 'bestellingen' (rolnaam). Echter, een specifieke instantie van Bestelling heeft een associatie met maar 1 instantie van Klant (1 bij pijlvoet). Tussen Bestelling en Artikel is dit anders want een artikel kan uiteraard geassocieerd zijn met meerderde bestellingen (ster bij pijlvoet). En bij Leverancier en Artikel is het weer anders.

Van use-cases naar ontwerp

De volgende stap in het maken van het klassendiagram is ervoor zorgen dat we functioneel gaan ontwikkelen wat we met de opdrachtgever afgesproken hebben. De functionele afspraken hebben we in de analysefase al vastgelegd in use-cases. Het is nu zaak om ervoor te zorgen dat elke use-case een operatie wordt in het klassendiagram.

Als voorbeeld neem ik hier het use-casediagram van een eenvoudige webwinkel uit het artikel over het use-casediagram. Mocht je niet meer helemaal weten hoe het ook al weer zat, kijk dan nog even terug in dat artikel.

full fig.8: Een use-casediagram (wellicht incompleet) voor een eenvoudige webwinkel.

Het use-casediagram zegt in dit geval dat er 2 verschillende use-cases zijn in het informatiesysteem. Nu zal dit use-casediagram waarschijnlijk incompleet zijn voor de gehele webwinkel, maar voor nu is dit voldoende om te laten zien hoe dit uitgewerkt wordt in het klassendiagram. Het is gebruikelijk om de naam van de toe te voegen operatie zo veel mogelijk te laten lijken op de use-case die deze operatie adresseert. Soms is dit onhandig of zit je met verschillende talen waardoor het niet precies hetzelfde kan. Dit is niet zo erg, als uit de naam van de operatie maar te begrijpen is welke use-case het is. Hieronder het klassendiagram met de use-case Bestelling plaatsen toegevoegd als operatie plaatsBestelling in de klasse Klant.

full fig.9: Het eerdere klassendiagram met de operatie plaatsBestelling toegevoegd aan de klasse Klant.

Het plaatsen van een bestelling wordt gedaan door een specifieke klant en bij het plaatsen is het ook relevant wie die klant is. In een dergelijk geval is het waarschijnlijk zo dat de klasse Klant verantwoordelijk is voor het bijhouden van de bestellingen van deze klant. Dit kun je zien aan het feit dat de klasse Klant een associatie heeft met de klasse Bestelling. Bij de pijlpunt van deze associatie staat tevens een sterretje, dus de klasse Klant is de eigenaar van een collectie van Bestelling met daarin de bestellingen die bij deze klant horen. Dit is de collectie waaraan deze use-case een bestelling wil toevoegen. De operatie dient dan, conform het encapsulatieprincipe, bij de eigenaar geplaatst te worden, in dit geval de klasse Klant.

Voor de use-case Artikelenlijst bekijken is het iets ingewikkelder, omdat dit niet een activiteit is die bij een specifieke klant hoort. Het maakt voor het informatiesysteem ook niet zo veel uit welke klant de artikelenlijst nu bekijkt. Daarnaast is het zo dat de beheerder ook de artikelenlijst kan bekijken. Het is uit het conceptueel model ook niet te begrijpen welk concept (en daarmee welke klasse) de eigenaar is van de artikelenlijst en dus beschikking heeft tot deze collectie van artikelen. Je zou kunnen zeggen dat Bestelling een collectie van Artikel heeft, en dat is correct, maar dit zijn niet alle artikelen, want deze collecte bevat uitsluitend de artikelen van deze specifieke bestelling. Er is dus in het huidige klassendiagram geen klasse aanwezig die de verantwoordelijkheid heeft om een lijst van alle artikelen vast te houden. In dit geval moeten we een klasse bijverzinnen die deze verantwoordelijkheid krijgt. We moeten namelijk een operatie toevoegen die deze use-case adresseert en een operatie dient aan een klasse toegevoegd te worden. In de figuur hieronder is de klasse ArtikelVerzameling toegevoegd, deze klasse is verantwoordelijk voor het vasthouden van alle artikelen en kan dus voorzien in de operatie geefArtikelLijst.

full fig.10: Het eerdere klassendiagram met de nieuwe klasse ArtikelVerzameling met daarin de operatie geefArtikelLijst.

Om aan te duiden dat de nieuwe klasse niet een concept is uit het conceptueel model, krijgt deze klasse een naam die niet een concept vertegenwoordigt, maar een beetje een gekke naam die de functie van de klasse omschrijft. In GRASP heet dit patroon 'pure fabrication'.

Attributen

Als laatste worden de attributen aan de klassen toegevoegd. In theorie kun je ook eerst de attributen toevoegen, maar omdat dit het klassendiagram soms onduidelijker maakt, is het wellicht handiger om overzicht te behouden om eerst de operaties toe te voegen en daarna pas de attributen. Bij het toevoegen van de attributen is het van belang om niet helemaal overboord te gaan en zo veel mogelijk attributen toe te voegen. De kunst is om enkel die attributen toe te voegen waar de klasse niet zonder kan. Het is namelijk geen enkel probleem om tijdens de implementatiefase alsnog attributen toe te voegen als dat nodig blijkt te zijn, maar het is wel vervelend als een essentieel attribuut tijdens het implementeren vergeten wordt. Tevens dient het klassendiagram een overzicht te geven, en als het een heleboel details heeft in de vorm van attributen dan werkt dat niet in het voordeel van de overzichtelijkheid.

full fig.11: Het eerdere klassendiagram aangevuld met attributen.

In overleg met de opdrachtgever is besloten dat een Klant, een Artikel en een Leverancier altijd een naam hebben, en dat een Artikel tevens altijd een prijs heeft. Gebruikelijk is dat deze attributen dan ook als parameters in de constructor van de klasse opgenomen worden. Dit laatste is echter een conventie en als je als ontwerper vermoedt dat de ontwikkelaars die het klassendiagram gaan implementeren dit misschien anders kunnen opvatten, dan is het aan te raden om dit expliciet te vermelden of om de constructoren als operaties op te nemen in het klassendiagram. Let wel op dat het diagram daardoor vlug minder overzichtelijk kan worden.

Conclusie

Het klassendiagram is een schematische weergave van het ontwerp van het informatiesysteem. Het combineert de concepten en de structuur uit het conceptueel model met de functionaliteiten die bepaald zijn door het use-casediagram. Op deze manier weten we zeker dat de structuur van het informatiesysteem straks overeenkomt met de gedachte van de opdrachtgever, en dat het informatiesysteem zal voorzien in alle afgesproken functionaliteiten.


  1. UML kent ook nog een primitief type UnlimitedNatural, maar deze wordt niet gebruikt voor attributen en parameters (UML v2.5.1, H20). ↩︎