12Uitgewerkt voorbeeld: DeelMobiel
De implementatie bij het diagram in 1.8. Prijzen: fiets € 0,10/min; scooter € 1,00 start + € 0,25/min; step € 0,50 start + € 0,20/min. Een elektrisch voertuig is alleen te huren bij minimaal 20% accu. Voertuig, Fiets, de exception en de logger staan in hoofdstuk 4, 8 en 9.
public abstract class ElektrischVoertuig : Voertuig, IOplaadbaar { // gedeelde accu-logica op één plek (geen dubbele code) private int _accuPercentage; protected ElektrischVoertuig(string id, string merk, int accu) : base(id, merk) { AccuPercentage = accu; // via property = validatie } public int AccuPercentage { get => _accuPercentage; set { if (value is < 0 or > 100) throw new ArgumentOutOfRangeException(nameof(value), "Accupercentage moet tussen 0 en 100 liggen."); _accuPercentage = value; } } public void LaadOp() => AccuPercentage = 100; public override string ToString() => $"{base.ToString()}, accu {AccuPercentage}%"; }
Uitleg
Scooter en step delen dezelfde accu-logica, dus die staat één keer in een abstracte tussenklasse. Die erft van Voertuig en implementeert IOplaadbaar. De constructor zet het percentage via de property, zodat de controle op 0 tot en met 100 ook bij aanmaken werkt.
abstract- Klasse kan niet met
newgemaakt worden; een abstracte methode heeft geen body en moet in de subklasse worden ingevuld. base(...)- Roept de constructor van de ouderklasse aan, vóór de eigen constructor draait.
base.Methode()- Roept de versie van de ouderklasse aan, bijvoorbeeld om die tekst aan te vullen.
protected- Zichtbaar in deze klasse en in subklassen, niet daarbuiten.
nameof- Geeft de naam van een variabele of parameter als tekst, en blijft kloppen als je hem hernoemt.
throw- Gooit een exception; de methode stopt direct en de fout gaat omhoog naar de aanroeper.
ArgumentOutOfRangeException- Voor een waarde buiten het toegestane bereik.
override- Vervangt een virtual of abstracte methode van de ouder; tijdens runtime wordt de versie van het echte object gekozen.
private- Alleen zichtbaar binnen deze klasse.
value- In een setter: de waarde die iemand probeert toe te kennen.
=>- 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.
public class Scooter : ElektrischVoertuig { private const decimal Starttarief = 1.00m; private const decimal PrijsPerMinuut = 0.25m; public string Kenteken { get; } public Scooter(string id, string merk, string kenteken, int accu) : base(id, merk, accu) { Kenteken = kenteken; } public override decimal BerekenPrijs(int minuten) => Starttarief + minuten * PrijsPerMinuut; public override string ToString() => $"{base.ToString()}, kenteken {Kenteken}"; }
Uitleg
De scooter hoeft alleen nog zijn eigen gegevens en prijsberekening te leveren; de accu komt van de tussenklasse. Tarieven staan in benoemde constanten met het m-achtervoegsel, zodat het decimals zijn.
base(...)- Roept de constructor van de ouderklasse aan, vóór de eigen constructor draait.
base.Methode()- Roept de versie van de ouderklasse aan, bijvoorbeeld om die tekst aan te vullen.
override- Vervangt een virtual of abstracte methode van de ouder; tijdens runtime wordt de versie van het echte object gekozen.
private- Alleen zichtbaar binnen deze klasse.
const- Vaste waarde die bij het compileren al vastligt en nooit verandert.
{ 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. ToString()- Tekstweergave van een object; wordt automatisch gebruikt bij
Console.WriteLine(obj).
public sealed class Verhuurbedrijf { private static Verhuurbedrijf? _instance; public static Verhuurbedrijf Instance => _instance ??= new(); private Verhuurbedrijf() { } private readonly List<Voertuig> _voertuigen = new(); public IReadOnlyList<Voertuig> Voertuigen => _voertuigen; // upcasting public ILogger Logger { get; set; } = new ConsoleLogger(); public void VoegToe(Voertuig v) { ArgumentNullException.ThrowIfNull(v); _voertuigen.Add(v); Logger.Info($"Toegevoegd: {v.Id}"); } public void Huur(string id) { Voertuig v = Zoek(id) ?? throw Weiger(id, $"Voertuig {id} bestaat niet."); if (!v.IsBeschikbaar) throw Weiger(id, $"Voertuig {id} is al verhuurd."); if (v is IOplaadbaar o && o.AccuPercentage < IOplaadbaar.MinimaleAccu) throw Weiger(id, $"Accu van {id} is te leeg ({o.AccuPercentage}%)."); v.IsBeschikbaar = false; Logger.Info($"Verhuurd: {id}"); } public decimal Retourneer(string id, int minuten) { Voertuig v = Zoek(id) ?? throw new ArgumentException($"Voertuig {id} bestaat niet.", nameof(id)); v.IsBeschikbaar = true; decimal prijs = v.BerekenPrijs(minuten); // dynamic dispatch Logger.Info($"Geretourneerd: {id} na {minuten} min"); return prijs; } public void LaadAllesOp() { List<IOplaadbaar> lijst = _voertuigen.OfType<IOplaadbaar>().ToList(); foreach (IOplaadbaar o in lijst) o.LaadOp(); // upcasting via interface Logger.Info($"{lijst.Count} voertuigen opgeladen"); } private Voertuig? Zoek(string id) => _voertuigen.FirstOrDefault(v => v.Id == id); private VoertuigNietBeschikbaarException Weiger(string id, string melding) { Logger.Fout(melding); // eerst loggen return new VoertuigNietBeschikbaarException(id, melding); } }
Uitleg
Het hart van het systeem. Singleton via Instance, voertuigen in een private lijst die alleen-lezen naar buiten gaat, en een logger via de interface. Huur controleert drie situaties en logt elke weigering voordat de exception wordt gegooid. LaadAllesOp haalt met OfType alleen de oplaadbare voertuigen op.
sealed- Van deze klasse mag niemand erven.
nameof- Geeft de naam van een variabele of parameter als tekst, en blijft kloppen als je hem hernoemt.
??- Geeft de rechterkant als de linkerkant null is.
??=- Vult de variabele alleen als hij nog null is.
IReadOnlyList<T>- Alleen-lezen weergave van een lijst: wel lezen en doorlopen, niet toevoegen of verwijderen.
FirstOrDefault- LINQ: eerste element dat voldoet, of null als er geen is.
OfType<T>()- LINQ: houdt alleen elementen van type
Tover en cast ze meteen; veilig downcasten. ToList()- Maakt van het LINQ-resultaat een nieuwe
List<T>. throw- Gooit een exception; de methode stopt direct en de fout gaat omhoog naar de aanroeper.
ArgumentException- Voor een ongeldig argument, zoals een lege tekst of verkeerde combinatie.
ThrowIfNull- Gooit meteen een
ArgumentNullExceptionals het argument null is. private- Alleen zichtbaar binnen deze klasse.
using DeelMobiel; // 1. Singleton aantonen var bedrijf = Verhuurbedrijf.Instance; var ook = Verhuurbedrijf.Instance; Console.WriteLine($"Zelfde object: {ReferenceEquals(bedrijf, ook)}"); // 2. Objecten (upcasting naar Voertuig) Voertuig fiets = new Fiets("F1", "Gazelle", 7); Voertuig scooter = new Scooter("S1", "Niu", "DX-123-B", 80); Voertuig step = new ElektrischeStep("E1", "Segway", 25, 10); bedrijf.VoegToe(fiets); bedrijf.VoegToe(scooter); bedrijf.VoegToe(step); // 3 + 4. Huren en retourneren bedrijf.Huur("F1"); Console.WriteLine($"Prijs fiets: € {bedrijf.Retourneer("F1", 30):0.00}"); // 3,00 bedrijf.Huur("S1"); Console.WriteLine($"Prijs scooter: € {bedrijf.Retourneer("S1", 20):0.00}"); // 6,00 // 5. Weigering afvangen try { bedrijf.Huur("E1"); } catch (VoertuigNietBeschikbaarException ex) { Console.WriteLine($"Melding aan klant: {ex.Message}"); } // 6. Opladen en opnieuw bedrijf.LaadAllesOp(); bedrijf.Huur("E1"); Console.WriteLine("Step is nu verhuurd."); // 7. Overzicht: overridden ToString() per subklasse foreach (Voertuig v in bedrijf.Voertuigen) Console.WriteLine(v);
Uitleg
De demonstratie: twee keer Instance ophalen bewijst de Singleton, de prijzen laten polymorfisme zien, de try/catch toont de eigen exception, en de laatste lus laat per object de juiste ToString() zien.
try / catchtrybevat code die kan falen;catchvangt een specifiek type fout af.ReferenceEqualstrueals twee variabelen naar precies hetzelfde object wijzen.$"...{x}"- String-interpolatie: zet de waarde van
xdirect in de tekst. var- De compiler leidt het type zelf af uit de rechterkant.
foreach- Loopt één voor één door alle elementen van een collectie.