Het gebruiken van interfaces

Nu we weten hoe we een interface maken, wat doen we er mee?

Stel dat we een programma schrijven waarin we een doel moeten bereiken met een object maar niet noodzakelijk weten hoe het object zijn functie zal realiseren. We weten wel welke interacties nodig zullen zijn met het object. Dan is een interface ideaal voor het object.

Bijvoorbeeld Ik moet naar Antwerpen geraken en heb een auto, ik kan een auto besturen maar weet eigenlijk niet (in detail) hoe die auto vooruit gaat. Wie weet zitten er wel hamsters onder de motorkap. Dankzij de interface, zijnde het dashboard, stuur en pedalen weet ik mijn plan te trekken.

Ons programma moet zijn gegevens opslagen, voor naslagwerk, backups, enz.. Er is een object waar we de gegevens naar moeten weg schrijven, en we weten hoe we dat object daarvoor moeten gebruiken. Er is namelijk een methode waar we alle data naartoe kunnen sturen. Maar wat de details van wegschrijven zullen zijn is niet volledig gekend, of het staat nog niet vast.

Soms moeten gegevens leesbaar kunnen bewaard worden, soms hebben we liever dat we het compact(binair/zip) weg schrijven en mogelijks zelfs geëncrypteerd, op de ene moment schrijven we ze naar onze harde schijf de andere keer naar een server of databank. Om dat mogelijk te maken in Java, kunnen we in elke situatie verschillende klassen gebruiken met een gelijknamige functie, en deze functie verwerkt in elke klasse dezelfde gegevens, dus heeft dezelfde parameters en return-type.
bv. void write(MyData data).

Dit zou een ideale situatie zijn voor de volgende interface:

public interface DataStorageInterface{
   void write(MyData data);
}

Vervolgens kunnen we dan een klasse maken die deze interface implementeert, zodat de functie write data leesbaar weg schrijft naar de harde schijf. Een andere implementatie-klasse schrijft de data geëncrypteerd naar een backup. En nog een andere die deze data bewaard op een SQL server.

bv:

public class DataFileStorage implements DataStorageInterface{
   void write(MyData data){
       // open file and write data to it
   }
}

In onze applicatie krijgen we zodoende een losse koppeling tussen onze gegevens en de wijze waarop de gegevens worden bewaard. Waardoor het zeer eenvoudig kan zijn om op een moment naar keuze, de mogelijkheid van de gegevens te bewaren te kunnen aanpassen & uitbreiden zonder veel complexiteit toe te voegen. En dankzij deze interface helpt de compiler ons te verzekeren dat alle gebruikte klassen, deze methode ‘ingevuld’ zullen hebben.

public class MyApp{
   MyData data;
   DataStorageInterface storage = new DataFileStorage("text.txt"); 
   // new EncryptedDataFileStorage("data.dat");
   // new MySQLDataBaseStorage();

   public static void main(String[] args){
       //...
       storage.write(data);
       //...
   }
}

Loose coupling

Zoals het voorbeeld hierboven worden interfaces gebruikt voor een “losse koppeling” te bekomen in een programma. Dit maakt het mogelijk om onderdelen van het programma aan te passen zonder daarbij heel het programma te moeten aanpassen. We schrijven een andere klasse die de interface implementeert, en koppelen de interface variabele met deze andere klasse.

Deze aanpak word aangeprezen onder meerdere noemers: “Loose coupling principle” en “Interface segregation principle” van SOLID.