1UML-klassendiagram
Een klassendiagram is de blauwdruk van je code ("draw before coding"). Alles wat in je code staat, moet in het diagram terug te vinden zijn, en andersom.
#1.1 Opbouw van een klasse
| Vak | Inhoud |
|---|---|
| 1. Naam | Klassenaam, vet. Eventueel stereotype erboven: «abstract», «interface», «enumeration». Abstract: naam cursief. |
| 2. Attributen | Velden en properties: zichtbaarheid naam: Type. Properties krijgen {get; set}. |
| 3. Operaties | Constructors en methodes: zichtbaarheid Naam(param: Type): ReturnType. Constructor heeft geen returntype. |
| Teken | Betekenis | C# |
|---|---|---|
| + | public | public |
| - | private | private |
| # | protected | protected |
| ~ | package / internal | internal |
| onderstreept | static | static |
| cursief | abstract | abstract |
| /naam | afgeleid / berekend | get-only property met berekening |
#1.2 Notatie-spiekbrief
| UML | C# |
|---|---|
| -_saldo: decimal | private decimal _saldo; |
| +Naam: string {get; set} | public string Naam { get; set; } |
| +Id: string {get} | public string Id { get; } (alleen in constructor te zetten) |
| +Naam: string {get; private set} | public string Naam { get; private set; } |
| +Zone: Zone? {get; internal set} | public Zone? Zone { get; internal set; } (? = mag null zijn) |
| +Voertuigen: IReadOnlyList<Voertuig> {get} | public IReadOnlyList<Voertuig> Voertuigen => _voertuigen; |
| +Fiets(id: string, merk: string) | public Fiets(string id, string merk) : base(id, merk) { } |
| #Voertuig(id: string) | protected Voertuig(string id) (constructor van abstracte klasse) |
| +BerekenPrijs(minuten: int): decimal | public abstract decimal BerekenPrijs(int minuten); |
| +LaadOp(): void | public void LaadOp() { ... } |
| +Instance: Verhuurbedrijf {get} | public static Verhuurbedrijf Instance { get; } |
| -MAX: int = 100 {readOnly} | private const int MAX = 100; |
| Doos<T> (of T in stippelvakje rechtsboven) | class Doos<T> |
| «enumeration» Ernst: Informational, Warning, Critical | enum Ernst { Informational, Warning, Critical } |
Twee schrijfwijzen voor types kom je tegen: UML-stijl naam: Type en C#-stijl Type naam. Kies er één en wees consequent. Werk je verder op een bestaand diagram, volg dan de stijl daarvan.
#1.3 Relaties (pijlen)
| Tekening | Naam en betekenis | In C# |
|---|---|---|
| Associatie Kennen elkaar (doorgetrokken lijn). | Property/veld naar het andere object. | |
| Gerichte associatie A kent B, B kent A niet. Open pijlpunt bij B. | List<Voertuig> in Bedrijf | |
| Aggregatie Geheel-deel; deel kan los bestaan. Open ruit bij het geheel. | Deel komt van buiten binnen: VoegToe(sensor) | |
| Compositie Geheel-deel; deel bestaat niet zonder het geheel. Gevulde ruit bij het geheel. | Geheel maakt het deel zelf: new Zone(...) binnenin | |
| Overerving (generalisatie) Is-een. Doorgetrokken lijn, open driehoek bij de ouder. | class Hond : Dier | |
| Realisatie Implementeert interface. Stippellijn, open driehoek bij de interface. | class Scooter : IOplaadbaar | |
| Afhankelijkheid Gebruikt tijdelijk (parameter, lokale variabele, throw). Stippellijn met open pijl, evt. «throws»/«create». | throw new ...Exception() |
#1.4 Multipliciteit
| Notatie | Betekenis | C# |
|---|---|---|
| 1 | precies één | verplichte property (niet null) |
| 0..1 | nul of één | Type? (nullable) |
| * of 0..* | nul of meer | List<T> |
| 1..* | minstens één | lijst + controle op leeg |
| 2..5 | bereik | lijst + controle op aantal |
Zet de multipliciteit aan beide uiteinden. Lees zo: "één Bedrijf heeft 0..* Voertuigen" (getal bij Voertuig).
#1.5 Compositie of aggregatie?
Vraag: bestaat het deel nog als het geheel weg is?
- Nee → compositie. Een Zone zonder Gebouw bestaat niet. Gebouw maakt de Zone:
VoegZoneToe(naam)doetnew Zone(naam). - Ja → aggregatie. Een sensor kan naar een andere zone verplaatst worden. Zone krijgt hem binnen:
VoegComponentToe(component). - Geen geheel-deel, alleen "kent"? → associatie.
#1.6 Abstracte klasse, interface en compositie in één voorbeeld
public abstract class Dier { public string Naam { get; set; } protected int Leeftijd; protected Dier(string naam) { Naam = naam; } public abstract void MaakGeluid(); // cursief in UML public void Adem() => Console.WriteLine("Ademt."); }
Uitleg
Een abstracte basisklasse: je kunt geen new Dier() doen, alleen subklassen maken. MaakGeluid() heeft geen body, dus elke subklasse moet hem zelf invullen. Adem() is gewone code die alle dieren gratis erven. De constructor is protected omdat alleen subklassen hem aanroepen.
abstract- Klasse kan niet met
newgemaakt worden; een abstracte methode heeft geen body en moet in de subklasse worden ingevuld. protected- Zichtbaar in deze klasse en in subklassen, niet daarbuiten.
=>- Korte schrijfwijze voor een methode of property die één expressie teruggeeft; in LINQ ook de pijl van een lambda.
public interface IVerzorgbaar { void Verzorg(); } public class Hond : Dier, IVerzorgbaar // eerst klasse, dan interfaces { public Halsband Halsband { get; } = new Halsband(); // compositie public Hond(string naam) : base(naam) { } public override void MaakGeluid() => Console.WriteLine("Woef!"); public void Verzorg() => Console.WriteLine($"{Naam} wordt geborsteld."); }
Uitleg
Hond erft van Dier (is-een) en belooft via de interface dat hij Verzorg() kan. In C# komt de basisklasse altijd eerst na de dubbele punt, daarna de interfaces. De Halsband wordt in de klasse zelf aangemaakt: dat is compositie (heeft-een).
base(...)- Roept de constructor van de ouderklasse aan, vóór de eigen constructor draait.
interface- Contract: een lijst met leden die een klasse belooft te leveren, zonder implementatie.
override- Vervangt een virtual of abstracte methode van de ouder; tijdens runtime wordt de versie van het echte object gekozen.
{ get; }- Alleen-lezen property; de waarde wordt in de constructor gezet.
=>- Korte schrijfwijze voor een methode of property die één expressie teruggeeft; in LINQ ook de pijl van een lambda.
$"...{x}"- String-interpolatie: zet de waarde van
xdirect in de tekst.
#1.7 Regels en valkuilen bij het tekenen
- Geërfde leden niet herhalen in de subklasse. Een methode zet je alleen opnieuw in de subklasse als die hem overridet.
- Methodes uit een interface staan wel in de klasse die ze implementeert (die klasse levert de code).
- Constructors in het diagram zetten, met parameters. Constructor van een abstracte klasse is
#(protected). - Een lijst mag als attribuut (
-_voertuigen: List<Voertuig>) en als pijl met0..*. Beide is gangbaar; wees consequent. - Exception = gewone klasse die erft van
Exception. Waar hij gegooid wordt: stippelpijl«throws»of een notitie. - Singleton herken je aan: private constructor (
-), static (onderstreept) veld_instanceen propertyInstance. - Gebruik notities (geel blaadje met omgevouwen hoek) voor regels als "accu 0 t/m 100" of "gooit exception als ...".
- Types altijd invullen, ook
void. Gebruikdecimalvoor bedragen.
#1.8 Volledig uitgewerkt voorbeeld: DeelMobiel
Een verhuurbedrijf met fietsen, scooters en elektrische steps. Het diagram combineert alles uit dit hoofdstuk: abstracte klassen, interfaces, overerving, realisatie, associatie met multipliciteit, een eigen exception, logging via een interface en een Singleton. De code staat in hoofdstuk 12.