25-12-2019
Interfaces
Interfaces standaardiseren delen van applicaties door afspraken vast te leggen. Zodat 2 of meer systemen aan elkaar gekoppeld kunnen worden. Men noemt interfaces ook wel eens “contracten”.
Eigenlijk zijn ze niet meer dan een set afspraken. Interfaces zijn het gemakkelijkst uit te leggen aan de hand van voorbeelden. Laten we daarvoor kijken naar een interface die we allemaal gebruiken.
Een interface in real life
Een die ik het leukste vind om uit te leggen is een stekker die je in het stopcontact steekt. Het zorgt ervoor dat we het ene ding aan het andere kunnen koppelen. Namelijk bijvoorbeeld een stofzuiger aan het elektriciteitsnet. De stekker in dit voorbeeld is een implementatie van een interface. Niet de interface zelf. Iemand heeft ooit vastgelegd dat een stekker pinnetjes moet hebben van 4,8mm dik, 19mm lang en 19mm uit elkaar. Die afspraken, noemt men een interface.
Waarom is dat handig? Nou, stel je voor dat we geen stopcontact en stekkers hadden? Dan moesten we de draadjes van onze stofzuiger bijvoorbeeld solderen aan die van het elektriciteitsnet. Dat is ten eerste gevaarlijk als je niet weet wat je doet, en ten tweede helemaal niet handig. Want je wil misschien ook wel eens iets anders aansluiten als alleen een stofzuiger.
Er kunnen ook meerdere implementaties zijn van de “stekker interface” zijn. In Nederland hebben we bijvoorbeeld stekkers van type F, dat zijn die ronde stekkers. Die zitten vaker aan je computer. En je hebt ook nog de platte stekker, van type C. Die vind je vaak bij nachtlampjes terug. Zolang iemand een implementatie maakt van die interface, past hij. Op dezelfde manier werkt het ook in softwareontwikkeling.
Interfaces in softwareontwikkeling
Technisch
Stel je voor dat we een userService class hebben en we hebben daarin een reeks functies zitten om gebruikers aan te maken, verwijderen, updaten en activeren. En dat de userService class een mailerService class gebruikt die een functie heeft met de naam sendMessage. Hiermee kan de userService mailtjes sturen naar de gebruiker om bijvoorbeeld een wachtwoord te resetten. Je zou een interface kunnen maken met de naam MessageHandlingInterface met daarin de functies sendMessage en getMessages. Die zou er dan zo uit kunnen zien als je PHP gebruikt:
<?php
interface MessageHandlingInterface {
public function sendMessage($subject, $message, $receiver);
public function getMessages()
}
Vervolgens kun je de mailerService class de interface laten implementeren. En hiermee geef je aan dat de mailerService de functies sendMessage en getMessages heeft:
class MailerService implements MessageHandlingInterface {
public function sendMessage($subject, $message, $receiver) {
//Send message code hier
}
public function getMessages() {
//Get messages code hier
}
}
Merk op dat implementeren anders is dan extenden aangezien er geen functionaliteit wordt overgeërfd van een parent class.
In de userService gebruik je de code “new mailerService()” om de mailerService te instantiëren. Doordat de mailService de interface implementeert, weten we dat deze die functies/methodes heeft die in de interface gedefinieerd staan. En dus dat je die functies zou moeten kunnen gebruiken.
Een extra voordeel, net als bij de stekkers is nu dat je 1 soort stekker hebt gemaakt, terwijl je er meer kunt maken die allemaal passen op de userService in dit geval. Je kunt zeggen: “ik heb een beter passende class om mails af te kunnen handelen”. Bijvoorbeeld een class die mails ontvangt via het IMAP-protocol in plaats van het POP3 protocol. Zolang de class de interface implementeert, zal hij werken met de userService. Want dat heb je immers met die interface afgesproken.
Interfaces en testen
Stel nou dat je wil gaan testen hoe je userService om gaat met de mailService. Dan wil je niet altijd een mail account aanmaken en die gaan overladen met test mailtjes. Soms wil je gewoon weten of de userService een “wachtwoord vergeten” mailtje zou sturen.
In dat geval kun je een nep mailService aanmaken die de interface implementeert. We noemen deze vaak mocks. Een mock faket in dit geval bijvoorbeeld het gedrag van je echte mailService en plaatst de mailtjes in een log bestand. In je userInterface maak je dan een nieuwe instantie van de nep mailService. En dan kun je testen! Lees documentatie maar eens door van je favoriete test framework. Er staat vrijwel altijd iets in over “mocking”.
Interfaces voor non-developers
Als software developer in een team hebben interfaces nog een voordeel die niet direct technisch van aard is.
Als je namelijk iets nieuws gaat maken met je team, ga je wel eens bespreken hoe iets er uit moet gaan zien. De resultaten van zulke gesprekken kun je verwerken in je interfaces! Zelfs de inzichten die je krijgt van niet technische mensen! Het kan bijvoorbeeld zo zijn dat iemand zegt: “Het maakt niet uit wat je doet, ik wil ook altijd kunnen zoeken naar email berichten”.
Een uitspraak zoals deze geeft aan dat je interface wellicht een search functie/methode moet hebben. Hoe je deze invult, gaf hij niet aan, maar kun je wel al vastleggen in je interface! Ook al heb je voor de rest nog geen regel code geschreven. Als je dan uiteindelijk je implementatie gaat maken, geeft de interface ook nog eens snel een overzichtje met wat je ook al weer allemaal moest maken.
Nog meer voordelen?
Interfaces hebben nog meer voordelen. Deze vallen echter wel buiten de scope van deze uitleg. Waar je op zou kunnen verder lezen is het gebruik van interfaces in dependency injection/inversion of control containers.
Nadelen?
Ja zeker. Die zijn er ook. Soms is het moeilijker te zien welke implementatie er gebruikt wordt. Je weet vaak wel dat er een bepaalde implementatie van een interface gebruikt wordt, maar niet exact welke. Als je geen interface zou gebruiken was het wat makkelijker te zien. Een goede editor of breakpoint debugging kan je in deze situaties vaak het snelste verder helpen.
Wanneer gebruik ik het beste een interface?
Die beslissing is onderhevig aan meningsverschillen. Maar ik zal toch een zo goed mogelijk gebalanceerd antwoord proberen te geven:
- Als je weet dat je implementatie spoedig weer zal veranderen, maar je interface (afspraken) niet.
- Als er nu al meerdere implementaties zijn met gemeenschappelijke functies. Of als je weet dat er meerdere gaan zijn (schaalbaarheid).
- Wanneer dezelfde functionaliteit in verschillende implementaties anders is.
- Wanneer je implementatie afhankelijk is van factoren die tests moeilijker kunnen maken (het voorbeeld van de mailservice bij het kopje “Interfaces en testen”).
- Soms in situaties waar teams veel waarde hechten aan gemaakte afspraken.