OOD Handboek

4Overerving en abstractie

Een subklasse (derived) erft velden, properties en methodes van de superklasse (base). C# staat één basisklasse toe, maar meerdere interfaces. Gebruik overerving alleen bij een echte is-een-relatie; bij heeft-een gebruik je compositie.

public abstract class Voertuig
{
    public string Id { get; }
    public string Merk { get; }
    public bool IsBeschikbaar { get; set; } = true;

    protected Voertuig(string id, string merk)   // protected!
    {
        if (string.IsNullOrWhiteSpace(id))
            throw new ArgumentException("Id mag niet leeg zijn.", nameof(id));
        Id = id;
        Merk = merk;
    }

    public abstract decimal BerekenPrijs(int minuten);  // geen body

    public virtual string Omschrijving() => "Voertuig";  // mag overriden

    public override string ToString() =>
        $"{GetType().Name} {Id} ({Merk}) - " +
        (IsBeschikbaar ? "beschikbaar" : "verhuurd");
}
Uitleg

De abstracte basis van alle voertuigen. Id en Merk zijn alleen-lezen en worden in de constructor gezet, met een controle op een leeg id. BerekenPrijs is abstract (elk voertuig rekent anders), Omschrijving is virtual (standaard aanwezig, mag aangepast). GetType().Name geeft de naam van de echte subklasse.

Functies en keywords
abstract
Klasse kan niet met new gemaakt worden; een abstracte methode heeft geen body en moet in de subklasse worden ingevuld.
virtual
Methode heeft een standaardversie die een subklasse mag vervangen met override.
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.
string.IsNullOrWhiteSpace
true als de tekst null, leeg of alleen spaties is.
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.
GetType().Name
Naam van het echte type van het object, ook als de variabele een ouder-type heeft.
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 x direct in de tekst.
public class Fiets : Voertuig
{
    private const decimal PrijsPerMinuut = 0.10m;
    public int Versnellingen { get; }

    public Fiets(string id, string merk, int versnellingen)
        : base(id, merk)               // eerst de ouder!
    {
        Versnellingen = versnellingen;
    }

    public override decimal BerekenPrijs(int minuten)
        => minuten * PrijsPerMinuut;

    public override string Omschrijving() => "Fiets";

    public override string ToString()
        => $"{base.ToString()}, {Versnellingen} versnellingen";
}
Uitleg

Een concrete subklasse. : base(id, merk) geeft de gegevens door aan de constructor van Voertuig, die eerst draait. Met override vul je de abstracte methode in en pas je ToString() aan, waarbij base.ToString() de tekst van de ouder hergebruikt.

Functies en keywords
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 x direct in de tekst.
ToString()
Tekstweergave van een object; wordt automatisch gebruikt bij Console.WriteLine(obj).
  • Volgorde: eerst constructor van de ouder, dan die van het kind.
  • Zonder : base(...) roept C# de parameterloze ouder-constructor aan. Bestaat die niet → fout CS7036.
  • base.ToString() hergebruikt de ouder-versie en vult aan.
  • sealed class X: niemand mag van X erven. sealed override: verder overriden verboden.

#virtual, abstract, override, new

Kenmerkvirtualabstract
Implementatie in basisklasseJa (standaardgedrag)Nee (geen body, eindigt op ;)
Moet overschreven wordenNee, magJa, door elke concrete subklasse
Klasse moet abstract zijnNeeJa
Gebruik alsje een standaard wilt die aanpasbaar iselke subklasse het verplicht anders doet

override = echte vervanging; wordt tijdens runtime gekozen op basis van het echte object (dynamic dispatch).

new (hiding) = verbergt de ouder-methode; wordt compile-time gekozen op basis van het type van de variabele. Bijna nooit wat je wilt.

// Stel: Dier heeft public virtual void Beweeg()
Dier d = new Hond("Rex");
d.Beweeg();  // override in Hond → "Hond rent."
             // new in Hond      → "Dier beweegt." (!)
Uitleg

Het verschil tussen override en new: bij override kijkt C# naar het echte object (een Hond), bij new naar het type van de variabele (Dier). Daarom geeft new hier onverwacht de Dier-versie.

#Abstracte klasse

  • Kan niet met new gemaakt worden (fout CS0144).
  • Mag velden, constructors, gewone én abstracte methodes hebben.
  • Bedoeld als gemeenschappelijke basis: "is een".
  • Gedeelde code in twee subklassen? Maak een abstracte tussenklasse (bv. ElektrischVoertuig : Voertuig, IOplaadbaar).

#Abstractie

Verberg complexe details en laat alleen het essentiële zien. In C# met abstracte klassen en interfaces. De gebruiker roept BerekenPrijs aan en hoeft niet te weten hoe elk voertuig dat doet.

#protected

#Leeftijd: bruikbaar in Dier en in Hond, maar niet in Program (fout CS0122).