Polymorfisme in de praktijk
Beeld je in dat je een klasse EersteMinister hebt met een methode Regeer en je wilt een eenvoudig land simuleren.
De EersteMinister heeft toegang tot de ministers die hem kunnen helpen (inzake milieu, binnenlandse zaken (BZ) en economie). Zonder de voordelen van polymorfisme zou de klasse EersteMinister er zo kunnen uitzien (slechte manier!):
internal class EersteMinister
{
public MinisterVanMilieu Jansens {get;set;} = new MinisterVanMilieu();
public MinisterBZ Ganzeweel {get;set;} = new MinisterBZ();
public MinisterVanEconomie VanCent {get;set;} = new MinisterVanEconomie();
public void Regeer()
{
// ministers stappen binnen en zeggen wat er moet gebeuren
// Jansens: Problematiek aangaande bos dat gekapt wordt
Jansens.VerhoogBosSubsidies();
Jansens.OpenOnderzoek();
Jansens.ContacteerGreenpeace();
// Ganzeweel advies omtrent rel aan grens met Nederland
Ganzeweel.VervangAmbassadeur();
Ganzeweel.RoepTroepenmachtTerug();
Ganzeweel.VerhoogRisicoZoneAanGrens();
// Van Cent geeft advies omtrent nakende beurscrash
VanCent.InjecteerGeldInMarkt();
VanCent.VerlaagWerkloosheidsPremie();
}
}Dit voorbeeld is gebaseerd op een briljante StackOverflow post waarin de vraag “What is polymorphism, what is it for, and how is it used?” wordt behandeld1.
De MinisterVanMilieu zou er zo kunnen uitzien (de methodenimplementatie mag je zelf verzinnen):
internal class MinisterVanMilieu
{
public void VerhoogBosSubsidies(){}
public void OpenOnderzoek(){}
}De MinisterVanEconomie-klasse heeft dan weer heel andere publieke methoden. En de MinisterBZ ook weer totaal andere.
Je merkt dat de EersteMinister-klasse aardig wat specifieke kennis moet hebben van de vele verschillende departementen van het land. Bovenstaande code is dus zeer slecht en vloekt tegen het abstractie-principe van OOP: onze klasse moeten veel te veel weten van andere klassen, wat vermeden moet worden. Telkens er zaken binnen een specifieke ministerklasse wijzigen moet dit ook in de EersteMinister aangepast worden. Dankzij polymorfisme en overerving kunnen we dit alles veel mooier oplossen!
Ten eerste: We verplichten alle ministers dat ze overerven van de abstracte klasse Minister die maar 1 abstracte methode heeft Adviseer:
internal abstract class Minister
{
abstract public void Adviseer();
}
internal class MinisterVanMilieu:Minister
{
public override void Adviseer()
{
VerhoogBosSubsidies();
OpenOnderzoek();
ContacteerGreenpeace();
}
private void VerhoogBosSubsidies(){ ... }
private void OpenOnderzoek(){ ... }
private void ContacteerGreenpeace(){ ... }
}
internal class MinisterBZ:Minister
{
public override void Adviseer()
{
Console.WriteLine("Zorg voor de binnenlandse veiligheid.");
}
}
internal class MinisterVanEconomie:Minister
{
public override void Adviseer()
{
Console.WriteLine("Verlaag de belastingen.");
}
}Merk op dat élke child van de abstracte Minister-klasse verplicht is om Adviseer te override’n. Een lege MinisterBZ : Minister {} zou niet compileren, want dan blijft de abstracte methode “leeg”.
Ten tweede: Het leven van de EersteMinister wordt plots véél makkelijker. Hij kan gewoon de Adviseer methode aanroepen van iedere minister:
internal class EersteMinister
{
public MinisterVanMilieu Jansens {get;set;} = new MinisterVanMilieu();
public MinisterBZ Ganzeweel {get;set;} = new MinisterBZ();
public MinisterVanEconomie VanCent {get;set;} = new MinisterVanEconomie();
public void Regeer()
{
Jansens.Adviseer();
Ganzeweel.Adviseer();
VanCent.Adviseer();
}
}En ten derde: En we kunnen hem nog helpen door met een array of List<Minister> te werken zodat hij ook niet steeds de “namen” van z’n ministers moet kennen. Dankzij polymorfisme mag dit:
internal class EersteMinister
{
public List<Minister> AlleMinisters {get;set;}= new List<Minister>();
public EersteMinister()
{
AlleMinisters.Add(new MinisterVanMilieu());
AlleMinisters.Add(new MinisterBZ());
AlleMinisters.Add(new MinisterVanEconomie());
}
public void Regeer()
{
foreach (Minister minister in AlleMinisters)
{
minister.Adviseer();
}
}
}En wie zei dat het regeren moeilijk was?!
Merk op dat dit voorbeeld ook goed gebruik maakt van compositie.
Stagiair Steven
Steven moet de ministers laten adviseren. Polymorfisme vond hij maar vaag, dus vroeg hij de A.I. om “iets dat zeker werkt”. Hij kreeg dit en plakt het in
Regeer:foreach (Minister minister in AlleMinisters) { if (minister is MinisterVanMilieu) { ((MinisterVanMilieu)minister).Adviseer(); } else if (minister is MinisterBZ) { ((MinisterBZ)minister).Adviseer(); } else if (minister is MinisterVanEconomie) { ((MinisterVanEconomie)minister).Adviseer(); } }“Zo weet ik zeker dat elke minister z’n juiste
Adviseerkrijgt”, zegt hij tevreden.
Steven z’n code werkt. Toch is dit precies wat je met polymorfisme wilt vermijden. Waarom?
Steven heeft met de hand nagebouwd wat polymorfisme gratis voor hem doet. Omdat élke Minister de methode Adviseer heeft, volstaat één regel: minister.Adviseer();. C# kiest dan zelf de juiste versie op basis van het echte type van het object. Steven z’n type-checks zijn niet enkel overbodig, ze zijn ook broos: voeg je morgen een MinisterVanDigitalisering toe, dan vergeet je gegarandeerd ergens een else if en valt die minister stilletjes weg. Net die afhankelijkheid wilde de hele les wegwerken. Steven liet de A.I. “iets dat werkt” maken in plaats van het concept te gebruiken dat hij zonet had gezien.
Steven moet de ministers laten adviseren. Polymorfisme vond hij maar vaag, dus vroeg hij de A.I. om “iets dat zeker werkt”. Hij kreeg dit en plakt het in