Wat leer je in dit hoofdstuk?

Na dit hoofdstuk kan je:

uitleggen wat een interface is en hoe ze verschilt van een klasse

een interface definiëren en in een klasse implementeren

kiezen tussen een interface en een abstracte klasse

meerdere interfaces combineren (waar overerving tekortschiet)

met is en as objecten op een interface bevragen

interfaces inzetten voor losser gekoppelde, SOLID-code

Interfaces in de echte wereld

“Interface” = tussen vlakken: de afgesproken verbinding tussen twee systemen.

Een auto

stuur en pedalen, ongeacht benzine of elektrisch

Een pc

USB, HDMI, audio, … wereldwijd afgesproken aansluitingen

Ken je de interface, dan kan je ze gebruiken zonder de interne werking te kennen.

Belangrijk

Dit gaat niet over grafische User Interfaces. U weze gewaarschuwd.

Interfaces in OOP

Een interface is een belofte: een lijst van publieke methoden en properties die een klasse zegt te hebben, zonder de implementatie.

Het is als een sticker USB 3.0 compatibel:

je weet wat het apparaat kan

niet hoe het binnenin werkt

Een interface in C#

interface ISuperHeld
{
    void SchietLasers();
    int VerlaagKracht(bool isZwak);
    int Power { get; set; }
}

I interface in plaats van class, naam start met een I

public Geen public (alles is publiek)

; Geen code: elke signatuur eindigt op ;

De regels

Geen instantievariabelen en geen constructors

Geen access modifiers: alles is public

Een interface erft niet van een klasse, wel van andere interfaces

In de regel geen code, enkel signaturen

Tip

Sinds C# 8 bestaan default interface methods (met code), maar we houden het bewust bij zuivere signaturen.

Interfaces en klassen

Een interface visualiseren

Een interface is als een blad met gaten: leg je het op een klasse die de belofte nakomt, dan vallen de gaten precies over de juiste methoden en properties.

De interface hangt aan de klasse.

Een klasse implementeert een interface

internal class Zorro : ISuperHeld
{
    public void RoepPaard() { }
    public bool HeeftSnor { get; set; }

    public void SchietLasers() { Console.WriteLine("pewpew"); }
    public int VerlaagKracht(bool isZwak)
    {
        if (isZwak) return 5;
        return 10;
    }
    public int Power { get; set; }
}

Zolang Zorro niet alles uit ISuperHeld implementeert, compileert de klasse niet.

Tip

In VS: klik op het lampje bij de klasse-signatuur en kies “Implement interface”.

Interface of abstracte klasse?

Abstracte klasse Interface
Gedeelde code ja nee
Instantievariabelen ja nee
Constructors ja nee
Hoeveel combineren? 1 meerdere
Relatie “is een” “kan iets”

Tip

Gedeelde code en een echte is-een-relatie? Abstracte klasse. Enkel afdwingen dat ongerelateerde klassen “iets kunnen” (IVliegt voor Vogel én Vliegtuig)? Interface.

UML en meerdere interfaces

Een klasse erft van maar één parent, maar mag meerdere interfaces dragen:

internal class Zorro : Man, ISuperHeld { }
internal class Batman : Man, ISuperHeld, ICoureur { }

Interface in UML (de “lolly”).

Eerst de parent-klasse, dan de interface(s).

Man

parent-klasse

ISuperHeld

interface

ICoureur

interface

Opmerking

Interfaces mogen ook van elkaar erven: interface IGod : ISuperHeld.

is met interfaces

if (gameOfThrones is IVerwijderbaar)
{
    Console.WriteLine("Ik kan dit verwijderen");
}

gameOfThrones is een Boek, en Boek implementeert IVerwijderbaar

Ik kan dit verwijderen

Net als bij polymorfisme schitteren interfaces in een lijst vol verschillende objecten:

foreach (var persoon in werknemers)
{
    if (persoon is IManager manager)
    {
        // enkel de managers
    }
}

Interfaces in de praktijk

Minister als bij-job

Een Minister zijn als “bij-job”, niet als hoofd-klasse. Definieer een interface:

interface IMinister
{
    void Adviseer();
}

Nu kan eender wie minister worden zonder z’n bestaande klasse op te geven:

internal class Ceo : IMinister
{
    public void Adviseer() { Console.WriteLine("Vrijhandel is essentieel!"); }
    public void MaakJaarlijkseOmzet() { Console.WriteLine("Geld!!!"); }
}

De premier werkt met IMinister

List<IMinister> alleMinisters = new List<IMinister>();
alleMinisters.Add(new Ceo());
alleMinisters.Add(new MinisterVanMilieu());

foreach (IMinister minister in alleMinisters)
{
    minister.Adviseer();
}

Een CEO én een echte minister staan samen in één lijst.

OOP benadert de realiteit.

.NET-interface: IComparable

Wil je Array.Sort of List.Sort op je eigen objecten? Dan moet je IComparable implementeren, anders krijg je:

At least one object must implement IComparable
internal class Land : IComparable
{
    public int Oppervlakte { get; set; }

    public int CompareTo(object other)
    {
        Land ander = (Land)other;
        return Oppervlakte.CompareTo(ander.Oppervlakte); // sorteer op oppervlakte
    }
}

Tip

Het framework roept jouw CompareTo aan. Zo zie je waarvoor interfaces echt dienen: een belofte die anderen (zelfs .NET zelf) kunnen gebruiken.

Alles samen: polymorfisme, interfaces en is/as

Alles samen: vloekende mensen

Mensen spreken; enkel vloekers vloeken:

Klasse-schema vloekende mensen.
interface IVloeker { void Vloek(); }

internal class Leraar : Mens { }
internal class Politieker : Mens, IVloeker
{
    public void Vloek() { Console.WriteLine("Godvermiljaardedju!"); }
}

Hoe laat je in een Mens[] enkel de vloekers vloeken?

Drie oplossingen

// 1) is + harde cast
if (mens is IVloeker)
    ((IVloeker)mens).Vloek();

// 2) as + null-check
IVloeker v = mens as IVloeker;
if (v != null) v.Vloek();

// 3) pattern matching (de moderne manier)
if (mens is IVloeker vloeker)
    vloeker.Vloek();
else
    mens.Spreek();

SOLID en de black-box

Werk tegen de interface, niet tegen de implementatie.

Het boeit je niet meer wat er in een klasse zit; aan haar interfaces zie je wat ze kan.

Dat is de kern van de SOLID-principes: abstractie en separation of concerns. De ultieme black-box-revelatie. See what I did there?

Interfaces overal in .NET

Het .NET-framework zit er vol mee. Een paar die je nog vaak zal tegenkomen:

IEnumerable

laat foreach en LINQ over je collectie lopen

IDisposable

netjes opruimen, samen met using

IComparable

maakt je objecten sorteerbaar

ICloneable

laat een object zichzelf klonen

Tip

Implementeer je zo’n interface, dan werkt je eigen klasse meteen samen met bestaande .NET-functionaliteit.

Conclusie

Een interface is een belofte van publieke methoden en properties, zonder code

Een klasse die een interface draagt, moet alles ervan implementeren

Kies een interface voor “kan iets” en om er meerdere te combineren; een abstracte klasse voor gedeelde code en “is een”

Met is en as (en pattern matching) bevraag je objecten op een interface

Interfaces zorgen voor losser gekoppelde, SOLID-code

Tip

Volgende halte: bestandsverwerking: lezen van en schrijven naar bestanden.

Meer weten

Kennisclips

Oefeningen H17 · Quizlet flashcards

Zie verder

Hoe doen andere programmeertalen hetzelfde? En een korte zoek-de-fout, om zo kritisch mogelijk naar code te leren kijken.

Zie verder: zoek de fout

Onderstaande C#-klasse zou IComparable moeten implementeren zodat Array.Sort werkt, maar de compiler klaagt dat Land de interface niet volledig implementeert. Wat ontbreekt?

internal class Land : IComparable
{
    public string Naam { get; set; }
    public int Oppervlakte { get; set; }

    public int Vergelijk(object obj)
    {
        Land temp = obj as Land;
        return Oppervlakte.CompareTo(temp.Oppervlakte);
    }
}

Opmerking

De methode heet Vergelijk, maar IComparable vereist exact de methode CompareTo(object obj). Een interface is een belofte op de letter: de naam (en signatuur) moet precies kloppen. Hernoem Vergelijk naar CompareTo en de klasse compileert weer.

Zie verder: interfaces elders

Java en TypeScript

hebben net als C# een echt interface-keyword:

interface ISuperHeld {
    schietLasers(): void;
    power: number;
}

Python

doet aan duck typing: geen verplichte interface, zolang een object toevallig de juiste methoden heeft, mag je het gebruiken.

C# laat de compiler je belofte hard controleren.

Zie verder: waarom maar één parent?

Dat je meerdere interfaces maar één klasse mag erven, koos Java ook bewust. C++ laat wél meervoudige overerving van klassen toe:

class Zorro : public Man, public SuperHeld { };  // mag in C++

Krachtig, maar het leidt tot het diamond problem: erven twee parents dezelfde methode, dan weet de compiler niet meer welke. Door enkel meervoudige interfaces (zonder eigen code) toe te laten, ontwijken C# en Java dat volledig.

Samenvattende poster

Een handige samenvattende poster (Opgelet: gemaakt m.b.v. ChatGPT Image Gen 2).