• Bij compositie maakt de klasse haar onderdelen zelf aan, met new bij de instantievariabele of in de constructor. Bij aggregatie krijgt ze het object binnen via een parameter of property, en het blijft ook zonder haar bestaan (uitleg).
  • Een interne lijst is een private instantievariabele, zonder property. Van buitenaf werk je ermee via je eigen methoden, zoals VoegToe. Een public get geeft de echte lijst, en daarop werkt Clear() gewoon, ook met private set (uitleg).
  • Een object als instantievariabele is null tot er een object in komt. Controleer op null voor je het gebruikt (uitleg).
  • this is het object zelf. Zo geef je jezelf mee aan een methode (coach.VoegSporterToe(this)), en this.naam = naam; lost een naamconflict met een parameter op. In een static methode bestaat this niet (uitleg).

UML naar code (Essential)

Een UML-diagram toont welke klasse welke andere klasse bevat. Zo lees je het:

  • Elk blokje wordt een klasse, ook als er verder niets in staat.
  • Een lijn met een volle ruit is compositie. De ruit staat aan de kant van de klasse die het onderdeel heeft. Die klasse maakt het onderdeel zelf aan: bij de instantievariabele of in de constructor.
  • Een lijn met een lege ruit is aggregatie. Het onderdeel wordt buiten de klasse aangemaakt en komt binnen via een property of een methode.
  • Het getal aan de kant van het onderdeel zegt hoeveel er zijn. 1..4 betekent minstens 1 en hoogstens 4. Zijn het er meer dan één, dan wordt het een List. Het getal aan de andere kant (0..1 bij Motor) moet je niet programmeren.
  • De onderdelen zijn private.

Deel 1. Een persoon heeft een hoofd, twee handen en twee benen. Geef Persoon een ToString die Persoon met 2 handen en 2 benen teruggeeft, en tel daarvoor de elementen in je lijsten.

UML-diagram: Persoon is met een volle ruit verbonden met Hoofd, Hand en Been.

Deze oplossing is afgeschermd.
Studenten krijgen de sleutel van hun lesgever. Heb je de sleutel al eens op dit toestel gebruikt, dan verschijnt de oplossing hier vanzelf.

Deel 2. Een groter diagram. Een motor krijgt in zijn constructor mee hoeveel zuigers hij heeft, een boot krijgt mee hoeveel schroeven hij heeft en hoeveel zuigers zijn motor heeft. Een auto heeft altijd een motor met 4 zuigers. Klopt een aantal niet met het diagram, dan werpt de constructor een ArgumentException op.

Geef Motor, Auto en Boot een ToString. Die van Auto en Boot gebruikt de ToString van hun motor.

UML-diagram met enkel volle ruiten: Auto heeft 4 Wiel en 1 Motor, Boot heeft 1 tot 4 Schroef en 1 Motor, Motor heeft 1 Krukas en 4 tot 8 Zuiger. Een motor zit in 0 of 1 auto en in 0 of 1 boot.

Testcode. Je code werkt als je exact deze uitvoer krijgt.

Auto auto = new Auto();
Console.WriteLine(auto);
Boot boot = new Boot(2, 6);
Console.WriteLine(boot);
try
{
    Boot vlot = new Boot(0, 4);
}
catch (ArgumentException e)
{
    Console.WriteLine(e.Message);
}
try
{
    Motor monster = new Motor(12);
}
catch (ArgumentException e)
{
    Console.WriteLine(e.Message);
}
Auto met 4 wielen en een motor met 4 zuigers
Boot met 2 schroeven en een motor met 6 zuigers
Een boot heeft 1 tot 4 schroeven.
Een motor heeft 4 tot 8 zuigers.

Deze oplossing is afgeschermd.
Studenten krijgen de sleutel van hun lesgever. Heb je de sleutel al eens op dit toestel gebruikt, dan verschijnt de oplossing hier vanzelf.

Deel 3. Dit diagram uit het boek combineert beide soorten ruiten. Een huis maakt zijn slaapkamer en badkamer zelf. De computer en zijn onderdelen worden buiten de klasse aangemaakt: Computer heeft daarvoor drie properties: Schijf van het type HardeSchijf, Processor van het type Processor en Geluidskaart van het type Geluidskaart. Slaapkamer heeft een property Computer van het type Computer. Een property mag dezelfde naam hebben als haar type. Omdat de slaapkamer private is, krijgt Huis een methode PlaatsComputer(Computer computer) die de computer in de slaapkamer zet.

ToString van Computer telt hoeveel onderdelen er al in zitten. ToString van Slaapkamer en van Huis zie je in de uitvoer hieronder.

UML-diagram: Huis heeft met een volle ruit een Slaapkamer en een Badkamer. Slaapkamer heeft met een lege ruit een Computer. Computer heeft met een lege ruit een Harde Schijf, een Processor en een Geluidskaart.

Testcode. Je code werkt als je exact deze uitvoer krijgt.

Computer pc = new Computer();
pc.Schijf = new HardeSchijf();
pc.Processor = new Processor();

Huis huis = new Huis();
Console.WriteLine(huis);
huis.PlaatsComputer(pc);
Console.WriteLine(huis);

huis = null; // het huis brandt af, de computer is gered
pc.Geluidskaart = new Geluidskaart();
Console.WriteLine(pc);
Huis met een badkamer en een slaapkamer zonder computer
Huis met een badkamer en een slaapkamer met computer met 2 onderdelen
computer met 3 onderdelen

Deze oplossing is afgeschermd.
Studenten krijgen de sleutel van hun lesgever. Heb je de sleutel al eens op dit toestel gebruikt, dan verschijnt de oplossing hier vanzelf.

  • Een volle ruit wordt een new in de klasse zelf, een lege ruit een property of methode waarlangs het object binnenkomt. Zie Associaties beschrijven.
  • Na huis = null; werkt pc gewoon verder: Main heeft nog een verwijzing naar de computer. De slaapkamer bereik je niet meer, die verdwijnt met het huis.
  • Een ToString mag de ToString van zijn onderdelen gebruiken: $"... een {motor}".

Voorspel de uitvoer: mag ik je laptop even? (Essential)

Groepswerk in de bib. De laptop van Tom doet het niet, dus werkt hij op die van Jana. Er zijn drie klassen:

internal class Batterij
{
    public int Procent { get; set; } = 100;
}
internal class Laptop
{
    private Batterij batterij = new Batterij();
    public string Sticker { get; set; }

    public Laptop(string Sticker)
    {
        Sticker = Sticker.ToUpper();
    }

    public int BatterijProcent
    {
        get { return batterij.Procent; }
    }

    public void Gebruik(int uren)
    {
        batterij.Procent -= uren * 20;
    }
}
internal class Student
{
    public string Naam { get; set; }
    public Laptop Laptop { get; set; }
}

En dit is Main:

Laptop laptop = new Laptop("ap");
Student jana = new Student() { Naam = "Jana", Laptop = laptop };
Student tom = new Student() { Naam = "Tom" };
Console.WriteLine($"Sticker: {laptop.Sticker}");

tom.Laptop = jana.Laptop;
tom.Laptop.Gebruik(3);
Console.WriteLine($"Jana: {jana.Laptop.BatterijProcent}%");

jana = null;
Console.WriteLine($"Tom: {tom.Laptop.BatterijProcent}%");

Student lotte = new Student() { Naam = "Lotte" };
Console.WriteLine($"Lotte: {lotte.Laptop.BatterijProcent}%");

Deel 1. Er zijn twee “heeft een”-relaties: een laptop heeft een batterij, een student heeft een laptop. Welke is compositie, welke aggregatie? Wijs telkens de lijn aan die het verraadt.

Deze oplossing is afgeschermd.
Studenten krijgen de sleutel van hun lesgever. Heb je de sleutel al eens op dit toestel gebruikt, dan verschijnt de oplossing hier vanzelf.

Deel 2. Schrijf op papier wat dit programma toont, lijn per lijn. Wat gebeurt er op de laatste lijn? Typ de code pas daarna over en vergelijk. De groene kronkels CS8618 en CS8600 mag je laten staan.

Deze oplossing is afgeschermd.
Studenten krijgen de sleutel van hun lesgever. Heb je de sleutel al eens op dit toestel gebruikt, dan verschijnt de oplossing hier vanzelf.

Deel 3. Herstel de constructor van Laptop met this, zodat de eerste lijn Sticker: AP toont. Pas daarna de laatste lijn van Main aan: heeft Lotte geen laptop, dan toont het programma Lotte heeft geen laptop.

Deze oplossing is afgeschermd.
Studenten krijgen de sleutel van hun lesgever. Heb je de sleutel al eens op dit toestel gebruikt, dan verschijnt de oplossing hier vanzelf.

  • Wie doet new? Doet de klasse het zelf, dan is het compositie. Komt het object binnen, dan is het aggregatie. Zie Associatie in de praktijk.
  • Een object toekennen maakt geen kopie: beide variabelen wijzen naar hetzelfde object, net als bij het hart van de donor in Associatie of overerving?
  • Heeft een parameter dezelfde naam als een property of instantievariabele, dan bereik je die laatste enkel nog met this. Zie Het this keyword.

Zelf je gaming-pc bouwen

Met het geld van je studentenjob stel je zelf een gaming-pc samen. Alles komt op het moederbord: een processor, een grafische kaart in het PCIe-slot, en RAM-latjes in de RAM-sloten.

Potloodtekening: de robot steekt een RAM-latje in een moederbord, het stokmannetje wijst met een vergrootglas naar een lege sleuf.

Maak deze klassen:

  • Processor en GrafischeKaart, elk met een Naam die je via de constructor meegeeft.
  • RamLatje met een Merk en een aantal Gigabyte, ook via de constructor.
  • Moederbord met een Naam en een aantal RAM-sloten, allebei via de constructor.

De processor en de grafische kaart koop je los en steek je erin: dat is aggregatie, dus Moederbord heeft voor elk een property Processor en GrafischeKaart. Zolang je er niets in stak, zijn ze null.

De RAM-sloten zijn een private array van RamLatje, met evenveel plaatsen als er sloten zijn. Een leeg slot is null. Van buitenaf kan je er enkel aan via twee methoden:

  • PlaatsRam(RamLatje latje) steekt het latje in het eerste vrije slot. Zijn alle sloten vol, dan toont ze Geen vrij RAM-slot meer voor de ... van ... GB.
  • TestMoederbord() toont wat er nog ontbreekt, plus hoeveel RAM-sloten vrij zijn en hoeveel gigabyte er in totaal geplaatst is.

Testcode. Je code werkt als je exact deze uitvoer krijgt.

Moederbord bord = new Moederbord("Z790 Gaming", 2);
bord.TestMoederbord();
Console.WriteLine("---");
bord.Processor = new Processor("Intel Core i5-14600K");
bord.PlaatsRam(new RamLatje("Corsair", 16));
bord.PlaatsRam(new RamLatje("Corsair", 16));
bord.PlaatsRam(new RamLatje("Kingston", 8));
bord.TestMoederbord();
Console.WriteLine("---");
bord.GrafischeKaart = new GrafischeKaart("GeForce RTX 4060");
bord.TestMoederbord();
Nog geen processor
Nog geen grafische kaart in het PCIe-slot
2 van de 2 RAM-sloten vrij, 0 GB geplaatst
---
Geen vrij RAM-slot meer voor de Kingston van 8 GB.
Nog geen grafische kaart in het PCIe-slot
0 van de 2 RAM-sloten vrij, 32 GB geplaatst
---
0 van de 2 RAM-sloten vrij, 32 GB geplaatst

Deze oplossing is afgeschermd.
Studenten krijgen de sleutel van hun lesgever. Heb je de sleutel al eens op dit toestel gebruikt, dan verschijnt de oplossing hier vanzelf.

  • “Heeft meerdere” kan ook een array zijn. Het aantal sloten ligt vast, dus de lengte van de array is meteen het aantal sloten, en null is een leeg slot. Zie “Heeft meerdere”-relatie.
  • Omdat de array private is, kan niemand een vijfde latje in een bord met vier sloten duwen: alles loopt langs PlaatsRam.
  • De processor en de grafische kaart komen binnen via een property, net als de harde schijf bij manier 3 in het boek. Daarom kan elk van beide null zijn, en controleert TestMoederbord dat eerst.

Politiek (Essential)

Maak een programma dat de regering van een land simuleert, met de klassen Land, Minister en President.

Minister. Een minister heeft enkel een autoproperty Naam.

President. Een president is een minister, met één extra autoproperty Teller (int) die op 4 start en een private setter heeft. Elke aanroep van de methode JaarVerder verlaagt de teller met 1.

Land. Een land heeft 0 of 1 president, 0 of 1 eerste minister en 0 tot 4 andere ministers in een List<Minister>. Die drie instantievariabelen zijn private. Ze worden niet in Land aangemaakt maar komen binnen via MaakRegering: het is dus aggregatie. Land heeft deze publieke methoden:

  • MaakRegering krijgt een president en een List<Minister> met 1 tot en met 5 ministers. De eerste minister in de lijst wordt de eerste minister van het land, de andere gaan in de eigen lijst van het land. Heeft het land al een president, dan toont de methode Gaat niet. Dit land heeft al een regering., en klopt het aantal ministers niet, dan Een regering heeft 1 tot 5 ministers nodig.. In beide gevallen verandert er niets.
  • JaarVerder roept JaarVerder op bij de president, als er een is. Staat zijn teller daarna op 0, dan toont de methode De regering is afgelopen.: de president en de eerste minister worden null en de lijst met ministers wordt leeggemaakt.
  • ToonRegering toont wie er in de regering zit, of Er is geen regering..

Testcode. Je code werkt als je exact deze uitvoer krijgt.

President tim = new President() { Naam = "Tim" };
List<Minister> ministers = new List<Minister>();
ministers.Add(new Minister() { Naam = "Bruno" });
ministers.Add(new Minister() { Naam = "Freya" });
ministers.Add(new Minister() { Naam = "Peter" });
ministers.Add(new Minister() { Naam = "Ann" });

Land mijnLand = new Land();
mijnLand.MaakRegering(tim, new List<Minister>());
mijnLand.MaakRegering(tim, ministers);
mijnLand.MaakRegering(tim, ministers);
mijnLand.ToonRegering();

for (int jaar = 1; jaar <= 4; jaar++)
{
    Console.WriteLine($"Jaar {jaar}");
    mijnLand.JaarVerder();
}
mijnLand.ToonRegering();
Een regering heeft 1 tot 5 ministers nodig.
Gaat niet. Dit land heeft al een regering.
President: Tim
Eerste minister: Bruno
Ministers: Freya, Peter, Ann
Jaar 1
Jaar 2
Jaar 3
Jaar 4
De regering is afgelopen.
Er is geen regering.

Deze oplossing is afgeschermd.
Studenten krijgen de sleutel van hun lesgever. Heb je de sleutel al eens op dit toestel gebruikt, dan verschijnt de oplossing hier vanzelf.

Denkvraag. Stel dat je land geen president heeft maar een koning. Kan Koning dan ook van Minister erven?

Deze oplossing is afgeschermd.
Studenten krijgen de sleutel van hun lesgever. Heb je de sleutel al eens op dit toestel gebruikt, dan verschijnt de oplossing hier vanzelf.

Deel 2: verkiezingen. Maak een klasse VerkiezingsUitslag met twee autoproperties met een private setter: VerkozenPresident (een President) en VerkozenMinisters (een List<Minister>). De default constructor vult ze zelf: een nieuwe president en 5 nieuwe ministers, elk met een willekeurige naam.

Tip 1, willekeurige naam: schrijf in de klasse een private string NaamGen()-methode die een string opbouwt met een willekeurige lengte (bv. tussen 5 en 10 tekens) waarbij elk teken een willekeurige kleine letter is (gebruik bv. (char)rng.Next('a', 'z' + 1)).

Tip 2, één Random: declareer Random als static variabele op klasseniveau (private static Random rng = new Random();). Zo delen alle objecten van de klasse één generator, zoals bij de Dobbelsteen in static en Random.

Nu is het opzetten van een regering in Main heel kort:

VerkiezingsUitslag uitslag = new VerkiezingsUitslag();
Land nieuwLand = new Land();
nieuwLand.MaakRegering(uitslag.VerkozenPresident, uitslag.VerkozenMinisters);
nieuwLand.ToonRegering();

De namen zijn telkens anders, bijvoorbeeld:

President: nmqkikrrc
Eerste minister: aruqzpwbc
Ministers: vqfnu, dmtshabfgj, efenbn, rrxuqap

Deze oplossing is afgeschermd.
Studenten krijgen de sleutel van hun lesgever. Heb je de sleutel al eens op dit toestel gebruikt, dan verschijnt de oplossing hier vanzelf.

  • Dezelfde ministers, bekeken vanuit twee klassen: VerkiezingsUitslag maakt ze zelf aan (compositie), Land krijgt ze binnen via MaakRegering (aggregatie).
  • MaakRegering neemt de ministers over in een eigen private lijst. Zo kan Main de regering niet meer veranderen via de lijst die het meegaf.
  • Controleer op null voor je de president aanspreekt: een land zonder regering heeft er geen. Zie NullReferenceException is een klassieke fout.

Stevens bibliotheek (Essential)

Stagiair Steven moest een bibliotheek programmeren. Een bibliotheek heeft boeken en leden, en een lid kan een boek lenen. Een boek dat uitgeleend is, mag niet uit de bibliotheek verdwijnen. Steven vroeg het aan een A.I. en plakte alles in zijn project:

internal class Boek
{
    public string Titel { get; private set; }
    public bool IsUitgeleend { get; set; }

    public Boek(string titel)
    {
        Titel = titel;
    }
}
internal class Lid : Boek
{
    private Boek uitgeleend;

    public string Naam { get; private set; }

    public Lid(string naam)
    {
        Naam = naam;
    }

    public void Leen(Boek boek)
    {
        uitgeleend = boek;
        boek.IsUitgeleend = true;
    }

    public override string ToString()
    {
        return $"{Naam} leest {uitgeleend.Titel}";
    }
}
internal class Bibliotheek
{
    // private set: zo kan niemand van buitenaf nog aan de boeken
    public List<Boek> Boeken { get; private set; } = new List<Boek>();
    private List<Lid> leden = new List<Lid>();

    public void VoegBoekToe(Boek boek)
    {
        Boeken.Add(boek);
    }

    public void VerwijderBoek(Boek boek)
    {
        if (boek.IsUitgeleend)
        {
            Console.WriteLine($"{boek.Titel} is uitgeleend en kan niet weg.");
        }
        else
        {
            Boeken.Remove(boek);
        }
    }

    public void SchrijfIn(Lid lid)
    {
        leden.Add(lid);
    }

    public static void ToonAantalBoeken()
    {
        Console.WriteLine($"Er staan {this.Boeken.Count} boeken in de bibliotheek.");
    }

    public void ToonLeden()
    {
        foreach (Lid lid in leden)
        {
            Console.WriteLine(lid);
        }
    }
}

In Main:

Bibliotheek bib = new Bibliotheek();
Boek avonden = new Boek("De Avonden");
bib.VoegBoekToe(avonden);
bib.VoegBoekToe(new Boek("Het Achterhuis"));
bib.VoegBoekToe(new Boek("Max Havelaar"));

Lid anna = new Lid("Anna");
Lid bram = new Lid("Bram");
bib.SchrijfIn(anna);
bib.SchrijfIn(bram);

anna.Leen(avonden);

// Test: een uitgeleend boek mag niet weg
bib.VerwijderBoek(avonden);
bib.Boeken.Remove(avonden);

Bibliotheek.ToonAantalBoeken();
bib.ToonLeden();

Deel 1. Het project compileert niet: de compiler geeft twee foutmeldingen. De waarschuwing CS8618 over uitgeleend mag je laten staan. Bij welke lijnen horen de twee fouten, wat betekenen de meldingen, en hoe herstel je ze? Opgelet: bij één van de twee is de snelste oplossing niet de juiste.

Deze oplossing is afgeschermd.
Studenten krijgen de sleutel van hun lesgever. Heb je de sleutel al eens op dit toestel gebruikt, dan verschijnt de oplossing hier vanzelf.

Deel 2. Nu compileert het. Steven verwacht deze uitvoer, want geen van beide lijnen onder // Test mag het boek weghalen:

De Avonden is uitgeleend en kan niet weg.
Er staan 3 boeken in de bibliotheek.
Anna leest De Avonden
Bram heeft geen boek

Voer het programma uit. Er zitten nog twee fouten in die de compiler niet vindt. Zoek ze en herstel ze.

Deze oplossing is afgeschermd.
Studenten krijgen de sleutel van hun lesgever. Heb je de sleutel al eens op dit toestel gebruikt, dan verschijnt de oplossing hier vanzelf.

  • De melding CS7036 wijst naar de constructor van Lid, maar de fout zit in : Boek. Klopt de “is een”-zin niet, dan is het een “heeft een”. Zie Associatie of overerving?
  • private set beschermt de property, niet de lijst erachter. Zie Stagiair Steven.
  • Een instantievariabele van een eigen klasse blijft null tot er een object in komt. Met enkel Anna in de bibliotheek had Steven de crash niet gezien. Zie NullReferenceException is een klassieke fout.
  • In een static methode bestaat this niet. Zie Het this keyword.

Worldbuilding (Essential, GPT)

Je werkt een vereenvoudigde spelwereld voor een RPG uit. Een wereld bestaat uit zones, zoals een betoverd bos of een brandende woestijn. In de zones liggen voorwerpen: wapens, drankjes, schatten.

  • Een wereld maakt en beheert zijn eigen zones: dat is compositie. Buiten de klasse Wereld krijgt niemand een zone in handen.
  • De voorwerpen bestaan ook los van een zone: een speler kan ze later meenemen, en ze kunnen naar een andere zone verhuizen. Dat is aggregatie.

ChatGPT generated. Prompt: genereer een bijhorende afbeelding in de stijl van een final fantasy achtige wereld. uiteraard zonder copyrights te schenden.

Deel 1: Voorwerp. Een Voorwerp heeft een Naam, een Gewicht (double, in kg) en een Waarde (int, in goudstukken), alle drie via de constructor. ToString geeft Magisch zwaard (3,5 kg, 1200 goudstukken). De static methode VergelijkWaarde(Voorwerp voorwerp1, Voorwerp voorwerp2) toont welk van de twee meer waard is, of dat ze evenveel waard zijn.

Deel 2: Zone. Een Zone heeft een Naam en een Moeilijkheid (1 tot 10), via de constructor, en een private lijst met voorwerpen. Methoden:

  • VoegVoorwerpToe(Voorwerp voorwerp) en VerwijderVoorwerp(Voorwerp voorwerp).
  • WaardevolleVoorwerpen(int minimum) geeft een nieuwe List<Voorwerp> terug met de voorwerpen die minstens zoveel waard zijn.
  • TotaalGewicht() geeft het gewicht van alle voorwerpen samen.
  • ToonInfo() toont de zone en haar voorwerpen, zoals in de uitvoer hieronder.

Deel 3: Wereld. Een Wereld heeft een Naam en een MinimumLevel, via de constructor, en een private lijst met zones. Methoden:

  • VoegZoneToe(string naam, int moeilijkheid) maakt zelf een nieuwe zone aan en voegt ze toe.
  • LegVoorwerp(string zoneNaam, Voorwerp voorwerp) legt een voorwerp in de zone met die naam, of toont De zone ... bestaat niet..
  • VerplaatsVoorwerp(Voorwerp voorwerp, string vanZone, string naarZone) haalt het voorwerp uit de ene zone en legt het in de andere.
  • ZonesMoeilijkerDan(int moeilijkheid) geeft een List<string> met de namen van de zones die moeilijker zijn.
  • ToonWaardevolleVoorwerpen(int minimum) toont per zone de voorwerpen van minstens die waarde. Ze gebruikt WaardevolleVoorwerpen van elke zone.
  • MagBinnen(int level) geeft true als een speler met dat level de wereld binnen mag.
  • ToonInfo() toont de wereld, en laat elke zone daarna zichzelf tonen.

Tip: drie methoden zoeken een zone op naam. Schrijf daarvoor één private methode die de zone teruggeeft, of null als ze niet bestaat.

Testcode. Je code werkt als je exact deze uitvoer krijgt.

Wereld mystica = new Wereld("Mystica", 10);
mystica.VoegZoneToe("Betoverd bos", 5);
mystica.VoegZoneToe("Brandende woestijn", 8);
mystica.VoegZoneToe("Bevroren meer", 7);

Voorwerp zwaard = new Voorwerp("Magisch zwaard", 3.5, 1200);
Voorwerp drankje = new Voorwerp("Helend drankje", 0.5, 150);
Voorwerp mantel = new Voorwerp("Zandmantel", 1.2, 300);
mystica.LegVoorwerp("Betoverd bos", drankje);
mystica.LegVoorwerp("Brandende woestijn", zwaard);
mystica.LegVoorwerp("Brandende woestijn", mantel);
mystica.LegVoorwerp("Vulkaan", mantel);
mystica.ToonInfo();

Console.WriteLine("--- Zones moeilijker dan 6");
List<string> moeilijk = mystica.ZonesMoeilijkerDan(6);
foreach (string naam in moeilijk)
{
    Console.WriteLine(naam);
}

Console.WriteLine("--- Voorwerpen van minstens 250 goudstukken");
mystica.ToonWaardevolleVoorwerpen(250);

Console.WriteLine("--- Vergelijk");
Voorwerp.VergelijkWaarde(zwaard, mantel);
Voorwerp.VergelijkWaarde(drankje, new Voorwerp("Rode appel", 0.2, 150));

Console.WriteLine("--- Het zwaard verhuist naar het bos");
mystica.VerplaatsVoorwerp(zwaard, "Brandende woestijn", "Betoverd bos");
mystica.ToonInfo();

Console.WriteLine("--- Wie mag binnen?");
Console.WriteLine(mystica.MagBinnen(8));
Console.WriteLine(mystica.MagBinnen(12));
De zone Vulkaan bestaat niet.
Wereld Mystica (vanaf level 10)
Betoverd bos (moeilijkheid 5, 0,5 kg aan voorwerpen)
  Helend drankje (0,5 kg, 150 goudstukken)
Brandende woestijn (moeilijkheid 8, 4,7 kg aan voorwerpen)
  Magisch zwaard (3,5 kg, 1200 goudstukken)
  Zandmantel (1,2 kg, 300 goudstukken)
Bevroren meer (moeilijkheid 7, 0 kg aan voorwerpen)
--- Zones moeilijker dan 6
Brandende woestijn
Bevroren meer
--- Voorwerpen van minstens 250 goudstukken
Magisch zwaard in Brandende woestijn
Zandmantel in Brandende woestijn
--- Vergelijk
Magisch zwaard is meer waard dan Zandmantel.
Helend drankje en Rode appel zijn evenveel waard.
--- Het zwaard verhuist naar het bos
Wereld Mystica (vanaf level 10)
Betoverd bos (moeilijkheid 5, 4 kg aan voorwerpen)
  Helend drankje (0,5 kg, 150 goudstukken)
  Magisch zwaard (3,5 kg, 1200 goudstukken)
Brandende woestijn (moeilijkheid 8, 1,2 kg aan voorwerpen)
  Zandmantel (1,2 kg, 300 goudstukken)
Bevroren meer (moeilijkheid 7, 0 kg aan voorwerpen)
--- Wie mag binnen?
False
True

Deze oplossing is afgeschermd.
Studenten krijgen de sleutel van hun lesgever. Heb je de sleutel al eens op dit toestel gebruikt, dan verschijnt de oplossing hier vanzelf.

  • Compositie in code: VoegZoneToe doet zelf new Zone(...), en Main krijgt nooit een zone in handen. Daarom werkt Main met de namen van de zones, en geeft ZonesMoeilijkerDan namen terug in plaats van zones.
  • Aggregatie in code: de voorwerpen worden in Main aangemaakt en komen binnen. Het zwaard verhuist naar het bos en blijft gewoon bestaan.
  • ToonInfo van de wereld laat elke zone zichzelf tonen: elke klasse doet haar eigen werk.
  • Filteren doe je met een lus die een nieuwe lijst vult, niet met LINQ (.Where()) (boeteblad).

Risk (Essential)

In het bordspel Risk verover je de wereld met legers die van land naar land trekken. Je maakt een vereenvoudigde versie met drie klassen.

Leger. Een leger heeft een Sterkte (int) die je via de constructor meegeeft. De sterkte is altijd positief: de setter werpt anders een ArgumentException op met de boodschap Een leger heeft een positieve sterkte.. Een leger weet ook waar het staat: een property Locatie van het type Land.

Land. Een land heeft een Naam, een private lijst met buurlanden en een private instantievariabele leger (null als er geen leger staat). Een property Leger met enkel een get geeft dat leger terug. Buurlanden en legers zijn aggregatie: verdwijnt een land, dan blijven zijn buren bestaan, en een leger kan gewoon naar een ander land trekken. Methoden:

  • VoegBuurToe(Land buur) en IsBuur(Land land).
  • PlaatsLeger(Leger leger) zet het leger in het land. Het land wijst dan naar het leger, en het leger moet ook weten dat het in dit land staat. Gebruik this, ook om de parameter leger en de instantievariabele leger uit elkaar te houden.
  • VerwijderLeger() maakt het land leeg.
  • ToString geeft bv. België (buurlanden: Nederland, Frankrijk, Duitsland), leger van 5, of geen leger op het einde.

Bordspel. Het bordspel heeft een private lijst met landen. De landen horen bij het spel en worden door het spel zelf aangemaakt: dat is compositie. Methoden:

  • VoegLandToe(string naam) maakt een nieuw land, voegt het toe aan de lijst en geeft het terug, zodat je er in Main mee kan spelen.
  • MaakBuren(Land land1, Land land2) maakt van beide landen elkaars buur, zodat de kaart langs twee kanten klopt.
  • ToonKaart() toont alle landen onder elkaar.
  • VerplaatsLeger(Land van, Land naar) verplaatst het leger van het ene land naar het andere. Staat er in van geen leger, of is naar geen buurland, dan werpt de methode een Exception op, zoals in de uitvoer hieronder. Staat er in naar al een leger, dan verdwijnt het verplaatste leger en krijgt dat andere leger zijn sterkte erbij.

Testcode. Je code werkt als je exact deze uitvoer krijgt.

Bordspel spel = new Bordspel();
Land belgie = spel.VoegLandToe("België");
Land nederland = spel.VoegLandToe("Nederland");
Land frankrijk = spel.VoegLandToe("Frankrijk");
Land duitsland = spel.VoegLandToe("Duitsland");
spel.MaakBuren(belgie, nederland);
spel.MaakBuren(belgie, frankrijk);
spel.MaakBuren(belgie, duitsland);
spel.MaakBuren(frankrijk, duitsland);

Leger blauw = new Leger(5);
belgie.PlaatsLeger(blauw);
frankrijk.PlaatsLeger(new Leger(3));
spel.ToonKaart();

Console.WriteLine("--- Blauw trekt naar Nederland");
spel.VerplaatsLeger(belgie, nederland);
Console.WriteLine($"Blauw staat in {blauw.Locatie.Naam}");
spel.ToonKaart();

Console.WriteLine("--- Frankrijk naar België, daarna naar Nederland");
spel.VerplaatsLeger(frankrijk, belgie);
spel.VerplaatsLeger(belgie, nederland);
spel.ToonKaart();

Console.WriteLine("--- Wat niet mag");
try
{
    spel.VerplaatsLeger(frankrijk, duitsland);
}
catch (Exception e)
{
    Console.WriteLine(e.Message);
}
try
{
    spel.VerplaatsLeger(nederland, frankrijk);
}
catch (Exception e)
{
    Console.WriteLine(e.Message);
}
try
{
    Leger spookleger = new Leger(0);
}
catch (Exception e)
{
    Console.WriteLine(e.Message);
}
België (buurlanden: Nederland, Frankrijk, Duitsland), leger van 5
Nederland (buurlanden: België), geen leger
Frankrijk (buurlanden: België, Duitsland), leger van 3
Duitsland (buurlanden: België, Frankrijk), geen leger
--- Blauw trekt naar Nederland
Blauw staat in Nederland
België (buurlanden: Nederland, Frankrijk, Duitsland), geen leger
Nederland (buurlanden: België), leger van 5
Frankrijk (buurlanden: België, Duitsland), leger van 3
Duitsland (buurlanden: België, Frankrijk), geen leger
--- Frankrijk naar België, daarna naar Nederland
België (buurlanden: Nederland, Frankrijk, Duitsland), geen leger
Nederland (buurlanden: België), leger van 8
Frankrijk (buurlanden: België, Duitsland), geen leger
Duitsland (buurlanden: België, Frankrijk), geen leger
--- Wat niet mag
In Frankrijk staat geen leger.
Frankrijk is geen buurland van Nederland.
Een leger heeft een positieve sterkte.

Deze oplossing is afgeschermd.
Studenten krijgen de sleutel van hun lesgever. Heb je de sleutel al eens op dit toestel gebruikt, dan verschijnt de oplossing hier vanzelf.

  • In PlaatsLeger doet this twee dingen: this.leger is de instantievariabele en niet de parameter, en leger.Locatie = this; geeft het land zelf mee aan het leger. Zo kloppen beide kanten na elke verhuis. Zie Het this keyword.
  • Bordspel maakt zijn landen zelf aan: compositie. Dat VoegLandToe het nieuwe land teruggeeft, verandert daar niets aan. Een leger komt binnen via PlaatsLeger: aggregatie.
  • Een land zonder leger heeft null in leger. Controleer daarop voor je naar de sterkte vraagt.

De Online Coach (Final Essentials, GPT)

Je bouwt de backend van een app voor personal training. Sommige objecten zijn eigendom van een ander object (compositie), andere wijzen gewoon naar elkaar (aggregatie).

Deel 1: de oefeningen en het schema. Een Oefening heeft een Naam, een Spiergroep en een AantalSets, via de constructor. ToString geeft Squat (benen) - 4 sets.

Een TrainingSchema heeft een SchemaNaam (via de constructor) en een private lijst met oefeningen. VoegOefeningToe(Oefening oefening) voegt er een toe. ToonVolledigSchema() toont de naam van het schema en daaronder elke oefening.

De oefeningen komen uit een catalogus in Main, en dezelfde oefening mag in meerdere schema’s zitten: dat is aggregatie.

Deel 2: de sporter. Een Sporter heeft een Naam en een eigen TrainingSchema, in een private instantievariabele. Het schema hoort bij de sporter: dat is compositie. Dus:

  • De constructor Sporter(string naam, string schemaNaam) maakt zelf een nieuw, leeg schema aan. Zo zit een sporter nooit zonder schema.
  • ZetNieuwSchema(string schemaNaam) vervangt het schema door een nieuw, leeg schema.
  • VoegOefeningToe(Oefening oefening) voegt een oefening toe aan zijn schema.
  • Train() toont ... traint., laat het schema zichzelf tonen en toont daarna Lekker getraind!.

Deel 3: de coach. Een Coach heeft een Naam en een private lijst met sporters. De coach bezit zijn sporters niet: stopt hij, dan trainen zij gewoon verder. Dat is aggregatie.

  • VoegSporterToe(Sporter sporter) voegt een sporter toe aan de lijst.
  • MotiveerIedereen() toont voor elke sporter [naam coach] roept: KOMAAN [naam sporter], nog eentje!.

Een sporter kiest zelf zijn coach: geef Sporter een methode KiesCoach(Coach coach) die de sporter zelf toevoegt aan de lijst van die coach.

Testcode. Je code werkt als je exact deze uitvoer krijgt. De laatste twee lijnen tonen het verschil met compositie: de coach stopt, en Lotte traint verder.

Oefening squat = new Oefening("Squat", "benen", 4);
Oefening deadlift = new Oefening("Deadlift", "rug", 3);
Oefening bench = new Oefening("Bench press", "borst", 4);

Sporter arnold = new Sporter("Arnold", "Full body");
arnold.VoegOefeningToe(squat);
arnold.VoegOefeningToe(deadlift);
Sporter lotte = new Sporter("Lotte", "Blokweek light");
lotte.VoegOefeningToe(squat);
lotte.VoegOefeningToe(bench);

Coach coleman = new Coach("Coleman");
arnold.KiesCoach(coleman);
lotte.KiesCoach(coleman);
coleman.MotiveerIedereen();
arnold.Train();

arnold.ZetNieuwSchema("Zomer cut");
arnold.VoegOefeningToe(bench);
arnold.Train();

coleman = null;
lotte.Train();
Coleman roept: KOMAAN Arnold, nog eentje!
Coleman roept: KOMAAN Lotte, nog eentje!
Arnold traint.
Schema Full body:
  Squat (benen) - 4 sets
  Deadlift (rug) - 3 sets
Lekker getraind!
Arnold traint.
Schema Zomer cut:
  Bench press (borst) - 4 sets
Lekker getraind!
Lotte traint.
Schema Blokweek light:
  Squat (benen) - 4 sets
  Bench press (borst) - 4 sets
Lekker getraind!

Deze oplossing is afgeschermd.
Studenten krijgen de sleutel van hun lesgever. Heb je de sleutel al eens op dit toestel gebruikt, dan verschijnt de oplossing hier vanzelf.

  • Het schema is compositie: enkel Sporter doet new TrainingSchema(...), en niemand anders krijgt het in handen. Een nieuw schema vervangt het oude, dat met niemand gedeeld was.
  • Oefeningen en sporters zijn aggregatie: ze worden in Main aangemaakt, komen binnen via een methode en kunnen in meerdere lijsten zitten. coleman = null; verandert niets aan Lotte.
  • coach.VoegSporterToe(this): de sporter geeft zichzelf mee. Zie Object geeft zichzelf mee als parameter.
  • Train laat het schema zichzelf tonen, en het schema laat elke oefening zichzelf tonen via ToString.