• Een interface is enkel een belofte: methoden en properties zonder public en zonder body, geen instantievariabelen en geen constructor. Haar naam begint met een I (uitleg).
  • De klasse die een interface aanhangt, schrijft alles ervan uit, en wel public. Achter het dubbele punt komt eerst de parentklasse, dan de interfaces: class Batman : Man, ISuperHeld (uitleg).
  • Een variabele, parameter of lijst mag een interface als type hebben, zoals List<IMinister>. Of een object een interface heeft, vraag je met is: if (mens is IVloeker vloeker) vloeker.Vloek(); (uitleg).
  • IComparable bestaat al in .NET: schrijf ze niet opnieuw, maar implementeer CompareTo. Geeft die een negatief getal, nul of een positief getal terug, dan werkt Sort() ook voor je eigen klasse (uitleg).

Stevens superhelden (Essential)

Stagiair Steven moest de eerste twee helden van een vechtspel maken. Batman en Zorro zijn allebei een Man, en ze beloven allebei wat een ISuperHeld belooft. Een A.I. schreef de code. Steven zette elk stuk in een eigen bestand en leverde het in zonder één keer te compileren:

//ISuperHeld.cs
interface ISuperHeld
{
    int power;
    public void SchietLasers();
    int Power { get; set; }
}
//Man.cs
internal class Man
{
    public void Wandel()
    {
        Console.WriteLine("Ik wandel rustig verder.");
    }
}
//Batman.cs
internal class Batman : ISuperHeld, Man
{
    public int Power { get; set; } = 80;

    public void SchietLasers()
    {
        Console.WriteLine("Batman heeft geen lasers en gooit een batarang.");
    }
}
//Zorro.cs
internal class Zorro : Man, ISuperHeld
{
    int Power { get; set; } = 60;

    public void SchietLasers()
    {
        Console.WriteLine("Zorro kerft een Z in de muur.");
    }
}
//Program.cs
static void Main(string[] args)
{
    List<ISuperHeld> helden = new List<ISuperHeld>();
    helden.Add(new Batman());
    helden.Add(new Zorro());
    helden.Add(new ISuperHeld());

    foreach (ISuperHeld held in helden)
    {
        held.SchietLasers();
        Console.WriteLine($"Power: {held.Power}");
        if (held is Man man)
        {
            man.Wandel();
        }
    }
}

Deel 1. Maak een nieuw project met een apart bestand voor de interface en voor elke klasse, en plak Stevens code erin. Er zitten vier fouten in die de compiler vindt. Lees de meldingen in de Error List en herstel ze. Let op: ze verschijnen niet allemaal tegelijk.

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. Alles compileert. Steven heeft nog drie vragen. Beantwoord ze eerst op papier, en test daarna.

  1. Een collega zegt dat public void SchietLasers(); in de interface ook fout is: “in een interface schrijf je geen access modifiers”. Heeft hij gelijk?

  2. Steven wil dat elke superheld met 100 power start. Zijn A.I. zet daarom een constructor in de interface:

    interface ISuperHeld
    {
        public void SchietLasers();
        int Power { get; set; }
    
        ISuperHeld()
        {
            Power = 100;
        }
    }

    Wat zegt de compiler, en waar hoort die beginwaarde dan wel?

  3. Steven wil van de interface een abstracte klasse SuperHeld maken, met internal class Batman : Man, SuperHeld. Waarom lukt dat niet?

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 een interface staan enkel de signaturen van methoden en properties: geen instantievariabelen, geen constructors en geen code. Zie Interface regels, met daaronder de tabel “Interface of abstracte klasse?”.
  • De interface schrijft geen public, maar de klasse die ze implementeert moet het wel doen. Anders telt het lid niet mee.
  • Eerst de parentklasse, dan de interfaces. Zie Meerdere interfaces.
  • Van een interface maak je geen object. Een variabele of een List<ISuperHeld> mag wel van dat type zijn, en daarin stop je objecten van klassen die de interface implementeren.
  • De compiler kijkt eerst of de interface en de klassen kloppen, en pas daarna naar de code in je methoden. Zolang er een fout in een klasse of interface staat, zie je de fout in Main dus niet. Visual Studio kijkt je code ook al na terwijl je typt, en kan de fout in Main daardoor soms wel al tonen.

CO2-teller van de campus (Essential)

De hogeschool wil weten hoeveel CO2 de campus uitstoot, en jij maakt de teller. Op de campus staan aula’s, computerlabo’s, pendelbussen en kamerplanten. Die vier hebben niets met elkaar gemeen: geen enkele erft van een andere. Op één ding na: drie van de vier stoten CO2 uit.

Maak een interface ICo2Uitstoot met twee methoden, allebei zonder parameters:

  • BerekenUitstoot geeft de uitstoot terug, als int.
  • VerlaagUitstoot geeft niets terug en maakt de uitstoot van dat object kleiner.

Maak vier klassen, elk in een eigen bestand:

Klasse Property Uitstoot Verlagen
Aula Volume (int, in kubieke meter) het volume maal een factor die start op 10 de factor zakt met 1, maar niet onder 1
Computerlabo AantalPcs (int) het aantal pc’s maal een factor die start op 100 de factor zakt met 10, maar niet onder 10
Pendelbus Motor, van een eigen enum MotorType met Diesel, Hybride en Elektrisch diesel 150, hybride 90, elektrisch 20 een dieselbus wordt hybride, een hybride bus elektrisch; elektrisch blijft elektrisch
Kamerplant geen krijgt de interface niet

De factor is een private instantievariabele: wie de klasse gebruikt, ziet ze niet. De uitstoot zelf bewaar je nergens, je berekent ze telkens opnieuw.

Zet in Main deze acht objecten in één lijst, in deze volgorde. Ze hebben enkel object gemeen, dus het wordt een List<object>:

List<object> campus = new List<object>();
campus.Add(new Kamerplant());
campus.Add(new Aula() { Volume = 300 });
campus.Add(new Computerlabo() { AantalPcs = 20 });
campus.Add(new Pendelbus() { Motor = MotorType.Diesel });
campus.Add(new Kamerplant());
campus.Add(new Aula() { Volume = 120 });
campus.Add(new Computerlabo() { AantalPcs = 35 });
campus.Add(new Pendelbus() { Motor = MotorType.Elektrisch });

Schrijf daarna een programma dat:

  1. van elk object de naam van zijn klasse en zijn uitstoot toont. De kamerplanten sla je over;
  2. de gemiddelde uitstoot van alle acht objecten berekent. De kamerplanten tellen mee, met een uitstoot van 0;
  3. toont welk object het meest uitstoot, met zijn index in de lijst;
  4. de uitstoot van elk object verlaagt en alles opnieuw toont.

Voorbeelduitvoer:

Aula: 3000
Computerlabo: 2000
Pendelbus: 150
Aula: 1200
Computerlabo: 3500
Pendelbus: 20
Gemiddelde uitstoot: 1233,75
Hoogste uitstoot: Computerlabo op index 6 (3500)
Na het verlagen:
Aula: 2700
Computerlabo: 1800
Pendelbus: 90
Aula: 1080
Computerlabo: 3150
Pendelbus: 20

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 aula, een pendelbus en een kamerplant hebben geen gemeenschappelijke parent: een kamerplant “is” geen aula. Een interface heeft dat ook niet nodig. Ze belooft enkel dat een klasse bepaalde methoden heeft. Zie Interfaces in OOP.
  • In een List<object> kan je per element enkel wat object kan. Met as of is vraag je of het de interface heeft, en pas dan roep je BerekenUitstoot op. Zie Alles samen: polymorfisme, interfaces en is/as.
  • GetType().Name geeft de naam van de klasse. GetType() alleen zet er ook de naamruimte van je project voor, zoals Co2Teller.Aula.

Kotfeest: wie draait de muziek? (Essential)

Vrijdagavond, kotfeest. Iedereen brengt iets mee: Jana haar smartphone, Bram zijn fototoestel en zijn oude wekkerradio, Sofie haar bluetoothbox. Je project heet Kotfeest, en elke interface en klasse staat in een eigen bestand:

interface ICamera
{
    void MaakFoto();
}
interface IMuziek
{
    void SpeelAf(string nummer);
}
internal class Toestel
{
    public string Eigenaar { get; set; }
}
internal class Smartphone : Toestel, ICamera, IMuziek
{
    public void MaakFoto()
    {
        Console.WriteLine($"Klik! (gsm van {Eigenaar})");
    }

    public void SpeelAf(string nummer)
    {
        Console.WriteLine($"Gsm van {Eigenaar} speelt {nummer}, blikkerig.");
    }
}
internal class Fototoestel : Toestel, ICamera
{
    public void MaakFoto()
    {
        Console.WriteLine($"Klik! Flits! (fototoestel van {Eigenaar})");
    }
}
internal class Bluetoothbox : Toestel, IMuziek
{
    public void SpeelAf(string nummer)
    {
        Console.WriteLine($"BOEM BOEM: {nummer}");
    }
}
internal class Wekkerradio : Toestel
{
    public void SpeelAf(string nummer)
    {
        Console.WriteLine($"De wekkerradio kraakt {nummer}.");
    }
}

En in Program.cs:

static void Main(string[] args)
{
    List<Toestel> opKot = new List<Toestel>();
    opKot.Add(new Smartphone() { Eigenaar = "Jana" });
    opKot.Add(new Fototoestel() { Eigenaar = "Bram" });
    opKot.Add(new Bluetoothbox() { Eigenaar = "Sofie" });
    opKot.Add(new Wekkerradio() { Eigenaar = "Bram" });

    foreach (Toestel toestel in opKot)
    {
        Console.Write($"{toestel.GetType().Name} van {toestel.Eigenaar}: ");
        if (toestel is ICamera)
        {
            Console.Write("camera ");
        }
        if (toestel is IMuziek)
        {
            Console.Write("muziek");
        }
        Console.WriteLine();
    }

    List<IMuziek> boxen = new List<IMuziek>();
    List<ICamera> cameras = new List<ICamera>();
    foreach (Toestel toestel in opKot)
    {
        if (toestel is IMuziek muziek)
        {
            boxen.Add(muziek);
        }
        if (toestel is ICamera camera)
        {
            cameras.Add(camera);
        }
    }
    StartPlaylist(boxen, "Dancing Queen");
    MaakGroepsfoto(cameras);
    Console.WriteLine(boxen[0] == cameras[0]);
}

static void StartPlaylist(List<IMuziek> boxen, string nummer)
{
    foreach (IMuziek box in boxen)
    {
        box.SpeelAf(nummer);
    }
}

static void MaakGroepsfoto(List<ICamera> cameras)
{
    foreach (ICamera camera in cameras)
    {
        camera.MaakFoto();
    }
}

Deel 1. Schrijf op papier wat dit programma toont, lijn per lijn. Typ de code pas daarna over en vergelijk. Beantwoord daarna nog twee vragen:

  1. De wekkerradio heeft toch een methode SpeelAf? Waarom speelt hij niet mee op het feestje?
  2. Wat moet je in de code van Wekkerradio toevoegen zodat hij wel meespeelt? Het is minder dan één regel.

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. Typ deze regels onderaan in je Main. Zeg eerst voor elke genummerde regel of ze compileert. Zo ja, wat toont ze? Zo nee, waarom niet? Test daarna. Een regel die niet compileert, zet je in commentaar voor je de volgende test.

IMuziek muziek = new Smartphone() { Eigenaar = "Jana" };
muziek.SpeelAf("Mr. Brightside");   // 1
muziek.MaakFoto();                  // 2
ICamera camera = muziek;            // 3
if (muziek is ICamera foto)         // 4
{
    foto.MaakFoto();
}

Tot slot: waarom is ICamera een interface, en geen klasse Camera met een methode MaakFoto?

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. Sofie brengt ook haar draaitafel mee. Een draaitafel kan alles wat een IMuziek kan, en daarnaast scratchen.

  • Maak een interface IDJ die erft van IMuziek en er één methode bij zet: void Scratch();. Hoe een interface van een andere erft, zie je bij IGod in Meerdere interfaces.
  • Maak een klasse Draaitafel die erft van Toestel en IDJ aanhangt. SpeelAf toont bijvoorbeeld De draaitafel van Sofie legt Dancing Queen op., Scratch toont Wikka wikka!.
  • Voeg in Main na de wekkerradio een draaitafel van Sofie toe aan opKot. Laat op het einde van Main elk toestel uit opKot dat een IDJ is, scratchen.

Voorspel eerst: komt de draaitafel in de playlist, ook al staat er nergens IMuziek achter de klasse? Haal de regels van deel 2 weg en voer uit. De wekkerradio uit deel 1 speelt intussen mee.

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 klasse erft van hoogstens één klasse, maar mag zoveel interfaces aanhangen als ze wil. De smartphone zit daardoor in een lijst van camera’s en in een lijst van muziektoestellen tegelijk. Zie Meerdere interfaces.
  • Een interface mag het type van een lijst of een parameter zijn, zoals List<ICamera>. MaakGroepsfoto hoeft niet te weten welke toestellen erin zitten. Zie Interfaces in de praktijk.
  • is kijkt naar de interfaces die een klasse aanhangt, niet naar de namen van haar methoden.
  • Via een variabele van een interface kan je enkel wat die interface belooft.
  • Een interface die van een andere erft, neemt al haar beloftes over.

Pokémon interfaces (Essential)

Shadow Pokémon (meer uitleg) zijn Pokémon met een duister hart. Je kan ze zuiveren (purify), en elke soort heeft daar een eigen manier voor. Je bouwt verder op je Pokemon-klasse uit Pokémon deel 2, met de constructor met zes base-stats.

  1. Maak een interface IShadow met een methode Purify (zonder parameters, zonder returnwaarde) en een read-only property IsGezuiverd van het type bool.
  2. Maak twee klassen die erven van Pokemon en IShadow aanhangen. Elke klasse heeft een default constructor die de base-stats via base(...) doorgeeft en de Naam invult:
    • ShadowLugia: naam Lugia, base-stats 106, 90, 130, 90, 154 en 110. Lugia is pas gezuiverd na drie keer Purify.
    • ShadowMewtwo: naam Mewtwo, base-stats 106, 110, 90, 154, 90 en 130. Mewtwo is gezuiverd na één keer Purify.
  3. Schrijf in Program twee static methoden die een List<Pokemon> krijgen:
    • ToonShadows toont van elke Shadow Pokémon die nog niet gezuiverd is Shadow: met de naam erachter. Is er geen enkele, dan toont ze Geen shadows meer.
    • ZuiverAlles roept van elke Shadow Pokémon in de lijst Purify op.

Testcode. Zet deze code in je Main. Je klassen werken als je exact deze uitvoer krijgt.

List<Pokemon> team = new List<Pokemon>();
team.Add(new Pokemon(35, 55, 40, 50, 50, 90) { Naam = "Pikachu" });
team.Add(new ShadowLugia());
team.Add(new Pokemon(45, 49, 49, 65, 65, 45) { Naam = "Bulbasaur" });
team.Add(new ShadowMewtwo());

Console.WriteLine("Voor het zuiveren:");
ToonShadows(team);
ZuiverAlles(team);
Console.WriteLine("Na 1 keer zuiveren:");
ToonShadows(team);
ZuiverAlles(team);
ZuiverAlles(team);
Console.WriteLine("Na 3 keer zuiveren:");
ToonShadows(team);
Console.WriteLine(team[1] is IShadow);
Voor het zuiveren:
Shadow: Lugia
Shadow: Mewtwo
Na 1 keer zuiveren:
Shadow: Lugia
Na 3 keer zuiveren:
Geen shadows meer.
True

Waarom heeft IShadow die property IsGezuiverd nodig? Kijk naar de laatste regel van de uitvoer. En waarom kan IShadow geen abstracte klasse Shadow zijn?

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 interface mag ook properties beloven, zonder body: bool IsGezuiverd { get; }.
  • Elke klasse vult de belofte op haar eigen manier in. Wie Purify oproept, hoeft niet te weten welke Shadow Pokémon hij voor zich heeft.
  • Eerst de parentklasse, dan de interface: ShadowLugia : Pokemon, IShadow. Zie Meerdere interfaces.
  • Pattern matching werkt ook met een interface: if (pokemon is IShadow shadow). Zie Alles samen: polymorfisme, interfaces en is/as.

Figuren sorteren (Essential)

Je bouwt verder op Geometrische figuren uit hoofdstuk 14: de abstracte klasse GeometrischeFiguur met Rechthoek, Vierkant en Driehoek. Maak een kopie van dat project. Je gaat een lijst figuren sorteren op oppervlakte, met de Sort-methode van List.

Stap 0: het probleem. Zet figuren.Sort(); in de testcode van hoofdstuk 14, net voor de foreach, en voer uit. Lees de foutmelding. Zet daarna : IComparable achter internal abstract class GeometrischeFiguur en compileer. Wat zegt de compiler nu?

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 1. Schrijf CompareTo in GeometrischeFiguur, zodat Sort de figuren van klein naar groot zet volgens hun oppervlakte. Is obj geen GeometrischeFiguur, dan gooi je een ArgumentException.

Onder CompareTo zet Visual Studio een groene lijn (CS8767). Die heeft met nullable types te maken, zoals CS8618 in hoofdstuk 9: je mag ze laten staan.

Testcode deel 1.

List<GeometrischeFiguur> figuren = new List<GeometrischeFiguur>();
figuren.Add(new Rechthoek(3, 4));
figuren.Add(new Vierkant(4));
figuren.Add(new Driehoek(3, 5));
figuren.Add(new Rechthoek(1, 2));
figuren.Add(new Driehoek(4, 4));
figuren.Sort();
foreach (GeometrischeFiguur figuur in figuren)
{
    Console.WriteLine(figuur);
}
Rechthoek van 2 op 1: 4 hoeken, oppervlakte 2
Driehoek van 5 op 3: 3 hoeken, oppervlakte 7,5
Driehoek van 4 op 4: 3 hoeken, oppervlakte 8
Rechthoek van 4 op 3: 4 hoeken, oppervlakte 12
Vierkant van 4 op 4: 4 hoeken, oppervlakte 16

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. Voeg in de testcode een Driehoek(4, 6) toe, na de andere figuren. Die heeft dezelfde oppervlakte als de Rechthoek(3, 4). Pas CompareTo aan: bij een gelijke oppervlakte komt de figuur met de minste hoeken eerst.

Rechthoek van 2 op 1: 4 hoeken, oppervlakte 2
Driehoek van 5 op 3: 3 hoeken, oppervlakte 7,5
Driehoek van 4 op 4: 3 hoeken, oppervlakte 8
Driehoek van 6 op 4: 3 hoeken, oppervlakte 12
Rechthoek van 4 op 3: 4 hoeken, oppervlakte 12
Vierkant van 4 op 4: 4 hoeken, oppervlakte 16

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. Nu van groot naar klein. Draai enkel de vergelijking van de oppervlakte om; bij een gelijke oppervlakte komt de figuur met de minste hoeken nog altijd eerst. Sorteer deze keer geen lijst, maar een array: vervang in de testcode van deel 2 figuren.Sort(); en de foreach door:

GeometrischeFiguur[] reeks = figuren.ToArray();
Array.Sort(reeks);
foreach (GeometrischeFiguur figuur in reeks)
{
    Console.WriteLine(figuur);
}
Vierkant van 4 op 4: 4 hoeken, oppervlakte 16
Driehoek van 6 op 4: 3 hoeken, oppervlakte 12
Rechthoek van 4 op 3: 4 hoeken, oppervlakte 12
Driehoek van 4 op 4: 3 hoeken, oppervlakte 8
Driehoek van 5 op 3: 3 hoeken, oppervlakte 7,5
Rechthoek van 2 op 1: 4 hoeken, oppervlakte 2

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.

  • Sort en Array.Sort sorteren je eigen objecten enkel als hun klasse IComparable aanhangt. Zonder die interface crasht Sort. Zie Sorteren met Array.Sort en de IComparable interface.
  • CompareTo geeft een negatief getal als het huidige object eerst moet komen, 0 als het gelijk is, en een positief getal als het na obj moet komen. Draai je de tekens om, dan draait de volgorde om.
  • Ook een abstracte klasse mag een interface aanhangen. Haar children erven de CompareTo gewoon mee.
  • IComparable bestaat al in .NET. Schrijf ze niet zelf opnieuw: jij levert enkel de CompareTo.

Hacker Simulator (Final Essentials, GPT)

Tijd om in de digitale schaduwen te duiken. Je bouwt een systeem om verschillende soorten computersystemen te infiltreren. Elk systeem heeft zijn eigen zwaktes, maar dankzij een universele interface kan je hacker-tool ze allemaal aan.

Potloodtekening: de robot in hoodie houdt één stekker omhoog die past op een bankautomaat, een server en een koelkast, het stokmannetje kijkt streng toe.

De namen in deze opgave zijn Engels. Houd dat vol in je eigen code, ook voor je private instantievariabelen: half Engels, half Nederlands kost punten.

Stap 1: de interface. Maak een interface IHackable met:

  • een read-only property SecurityLevel (int): hoe moeilijk het systeem te kraken is, van 1 tot 100;
  • een methode bool TryHack(string password): probeert in te breken en geeft true terug als dat lukt;
  • een methode void ShowData(): toont de geheime data als het systeem gehackt is, en anders ACCESS DENIED.

Stap 2: de doelwitten. Maak drie klassen die IHackable implementeren. Elk doelwit onthoudt met een bool of het al gehackt is. Eenmaal gehackt, blijft TryHack true teruggeven, welk wachtwoord er ook komt.

  • BankServer: SecurityLevel 90. TryHack lukt met het wachtwoord Money123 of met BACKDOOR. ShowData toont Transacties: Miljoenen gestolen!.
  • SocialMediaAccount: SecurityLevel 30. Het wachtwoord krijgt het account mee via zijn constructor, want elk account heeft een ander. TryHack lukt met dat wachtwoord. ShowData toont DM geschiedenis: Genante selfies gevonden..
  • SmartFridge (een IoT-toestel): SecurityLevel 5. TryHack lukt altijd, wat het wachtwoord ook is. ShowData toont Melk is op. Temperatuur: 4 graden..

Stap 3: de brute force tool. Maak een klasse HackerTool met een methode RunBruteForce(List<IHackable> targets). Die loopt door de lijst en probeert bij elk doelwit de veelvoorkomende wachtwoorden 123456, password, admin en Money123, in die volgorde. Zodra er een lukt, stopt ze met proberen bij dat doelwit. Gebruik daarvoor een while met een bool, geen break. Dan:

  • lukte het: toon SYSTEM BREACHED: met de naam van de klasse erachter, roep ShowData() op en toon Security Level was: met het niveau;
  • lukte niets: toon Failed to crack system..

HackerTool kent enkel de interface. Ze vraagt nooit met is welk soort doelwit ze voor zich heeft.

Testcode. Zet deze code in je Main. Je klassen werken als je exact deze uitvoer krijgt.

List<IHackable> targets = new List<IHackable>();
targets.Add(new BankServer());
targets.Add(new SocialMediaAccount("123456"));
targets.Add(new SocialMediaAccount("Zomer2024!"));
targets.Add(new SmartFridge());

targets[1].ShowData();
HackerTool tool = new HackerTool();
tool.RunBruteForce(targets);
Console.WriteLine(targets[1].TryHack("wrong"));
ACCESS DENIED
SYSTEM BREACHED: BankServer
Transacties: Miljoenen gestolen!
Security Level was: 90
SYSTEM BREACHED: SocialMediaAccount
DM geschiedenis: Genante selfies gevonden.
Security Level was: 30
Failed to crack system.
SYSTEM BREACHED: SmartFridge
Melk is op. Temperatuur: 4 graden.
Security Level was: 5
True

Stap 4: de vraag. Waarom is IHackable een interface, en geen abstracte klasse Hackable?

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.

  • HackerTool werkt met een List<IHackable> en weet niet welke doelwitten erin zitten. Komt er morgen een klasse SmartWatch bij die IHackable aanhangt, dan verandert er in HackerTool niets.
  • Ook een property hoort in het contract: int SecurityLevel { get; }, zonder body.
  • Kies een interface als klassen enkel een belofte delen, een abstracte klasse als ze ook code of instantievariabelen delen. Zie de tabel “Interface of abstracte klasse?” onder Interface regels.

PokéDex met foreach (PRO)

Deze oefening gaat verder dan de leerstof: IEnumerable zelf implementeren staat niet in het boek.

Maak een klasse PokeDex (in code zonder é) met een private List<Pokemon> en een methode VoegToe(Pokemon pokemon). Deze code moet werken met je Pokemon-klasse:

PokeDex dex = new PokeDex();
dex.VoegToe(new Pokemon(35, 55, 40, 50, 50, 90) { Naam = "Pikachu" });
dex.VoegToe(new Pokemon(45, 49, 49, 65, 65, 45) { Naam = "Bulbasaur" });
foreach (Pokemon pokemon in dex)
{
    Console.WriteLine(pokemon.Naam);
}
Pikachu
Bulbasaur

Zonder meer zegt de compiler CS1579: foreach statement cannot operate on variables of type 'PokeDex' because 'PokeDex' does not contain a public instance or extension definition for 'GetEnumerator'. Zoals Sort een CompareTo nodig heeft, heeft foreach een GetEnumerator nodig. Die methode belooft de interface IEnumerable (documentatie). Twee tips:

  • IEnumerable staat in System.Collections: zet using System.Collections; bovenaan het bestand van je PokeDex. Vergeet je dat, dan denkt de compiler dat je de versie uit System.Collections.Generic bedoelt, en krijg je CS0305.
  • De interface heeft één methode: IEnumerator GetEnumerator(). Je private lijst heeft die methode al.

Werkt het? Vervang dan in de foreach Pokemon pokemon door var pokemon. Wat zegt de compiler, en waarom?

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.

Game (PRO)

Een groter project voor wie nog wil: een klein spel op een kaart van 20 bij 20 vakjes. Er is geen oplossing. Het klasseschema en de interfaces staan onderaan deze opgave.

Het spel.

  • De speler (X) start in kolom 0, rij 10: links, in het midden. Hij wint als hij de overkant haalt, kolom 19.
  • Op elk vakje staat hoogstens één element: de speler, een rots (O), een monster (M) of een RockDestroyer (D). Een leeg vakje is null.
  • Bij de start komen er rotsen en monsters op willekeurige vakjes, maar niet in kolom 0. Hoeveel, kies je zelf.
  • Per beurt doet de speler één ding: één vakje bewegen (omhoog, omlaag, links of rechts) of naar rechts schieten. Bij een schot verdwijnt wat er op het vakje rechts van hem staat, een rots of een monster. Hij kan niet door rotsen of monsters wandelen, en niet van de kaart af.
  • Daarna doet elk monster één ding:
    • Een gewoon monster beweegt één vakje in een willekeurige richting. Is dat vakje bezet of ligt het buiten de kaart, dan blijft het staan.
    • Een RockDestroyer kijkt eerst naar het vakje links van hem. Staat daar een rots of de speler, dan schiet hij, en verdwijnt wat daar stond. Anders beweegt hij zoals een gewoon monster.
  • Wordt de speler neergeschoten, dan is het spel verloren.

Eén beurt.

  1. Vraag de speler wat hij wil doen: U (omhoog), D (omlaag), L (links), R (rechts) of S (schieten).
  2. Voer dat uit.
  3. Staat de speler in kolom 19? Dan is hij gewonnen.
  4. Laat elk monster zijn ene actie doen.
  5. Is de speler neergeschoten? Dan is hij verloren.
  6. Teken de kaart opnieuw.

Werk in delen.

  • Deel 1: de klassen uit het schema, een kaart MapElement[,] van 20 bij 20 met rotsen en de speler erop, en een methode die de kaart tekent.
  • Deel 2: de speler beweegt en schiet.
  • Deel 3: de monsters, eerst de gewone, daarna de RockDestroyers.
Tip

De meeste logica komt in je Main of in een klasse Manager. De klasse Player kan niet beslissen wat een monster doet: dan zou de speler als het ware in het hoofd van het monster zitten, en dat is tegen het idee van OOP. Voor het toeval gebruik je één private static Random.

Extra’s. Dit is een minimale beschrijving. Voeg naar believen toe:

  • de speler heeft levens;
  • monsters hebben levens;
  • andere soorten monsters, bijvoorbeeld slimmere;
  • meerdere levels, met telkens andere of meer monsters;
  • meerdere spelers;
  • verder schieten, of in andere richtingen.

Klasseschema.

Klassenschema: de abstracte klasse MapElement met instantievariabele drawChar, property Location en methoden Draw en de constructor. Monster, Player en Rock erven van MapElement, RockDestroyer erft van Monster. Monster implementeert IMoveable, Player IMoveable en IDestroyer, RockDestroyer IDestroyer.

Location is van het type Point (compositie). Point is een zelfgemaakte mini-klasse die er minimaal zo uitziet:

class Point
{
  public int X{get;set;}
  public int Y{get;set;}
}

In je abstracte klasse MapElement komt dus iets in de trend van public Point Location {get;set;} = new Point();.

De interfaces:

Interfaces

Het spel in actie. Deze versie spreekt de speler bij naam aan. Dat mag, maar hoeft niet.

Het spel in actie

Vraag. Eén van de twee interfaces had ook een abstracte klasse tussen MapElement en Monster/Player kunnen zijn. Welke, en waarom kan de andere enkel een interface zijn?

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.

  • Wat elk element heeft, een plaats en een manier om getekend te worden, staat in de abstracte klasse MapElement. Wat maar sommige elementen kunnen, bewegen en schieten, staat in een interface.
  • Een speler die beweegt én schiet, hangt twee interfaces aan: Player : MapElement, IMoveable, IDestroyer.