[oop] interface implementeren
Ik heb een vraagje en wellicht kan iemand mij een duwtje in de goede richting geven.
Hoe moet je precies een interface gebruiken?
Maak je een interface op method niveau, of op class niveau?
Is het bijvoorbeeld de bedoeling dat je een datacollectionInterface maakt, met als methods get() en set(). Of is het de bedoeling dat je een getInterface en een setInterface, en dan eventueel een getSetInterface die die 2 andere interfaces extend?
Ik wil vooral de achterliggende gedachte van een interface proberen te begrijpen. Ik hoop dat iemand dat op een simpele manier kan uitleggen, want ik ben een beetje bang dat ik het anders verkeerd aanpak, om er vervolgens over een paar weken achter te komen dat ik alles opnieuw kan doen.
Kan iemand me een beetje wegwijs maken? Hoeft echt geen lang verhaal te zijn, maar met name hoe ik mijn interfaces het beste kan opbouwen. Even een zetje in de goede richting zeg maar. Alvast bedankt.
Wat me opvalt is dat er mensen zijn die een interface gebruiken om één bepaalde method af te dwingen, en dat er mensen zijn die een interface gebruiken om de invulling van een class af te dwingen.
Een voorbeeld van het afdwingen van één method: printableInterface (de class moet een print method hebben)
Een voorbeeld van het afdwingen van een class: databaseInterface (de class moet een setQuery en execcute method hebben)
Eigenlijk heb ik 2 vragen:
1) gebruik je normaal gesproken beide methodes?
2) wanneer gebruik je een interface? Hoe weet je vantevoren of je een interface nodig hebt?
Code (php)
Op deze manier verplicht je dat een klasse bepaalde methodes moet implementeren maar je bent vrij om zelf te bepalen hoe je deze invult.
interface Countable dwingt alleen de methode count() af om te kunnen gebruiken in count().
interface Serializable dwingt methoden serialize() en unserialize() af om het te kunnen gebruiken in de serialize() en unserialize() functies.
interface Iterator dwringt methoden current(), next(), key(), rewind() en valid() af het te gebruiken in een foreach.
Moet ik bijv. een losse getInterface, hasInterface en setInterface maken, en dan vervolgens een dataInterface die deze 3 interfaces extend?
Daarnaast vraag ik me ook af hoe je een interface moet noemen. Want hierboven zie ik "...able" interfaces. CountABLE, SerializABLE. Maar het kan ook een zelfstandig naamwoord zijn: IteratorInterface, DatabaseInterface. Zijn daar bepaalde redenen voor?
En je moet ook niet voor alles een interface maken, dat is niet altijd nodig. Als je bijvoorbeeld verschillende type's classen hebt, bv.:
Code (php)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<?php
interface CarInterface {
public function getNaam();
public function getWielen();
}
class Volvo implements CarInterface {
public function getNaam() { return "Volvo"; }
public function getWielen() { return 4; }
}
class Citroen implements CarInterface {
public function getNaam() { return "Citroen"; }
public function getWielen() { return 4; }
}
$autos = [new Volvo(), new Citroen()];
foreach($autos as $auto) {
if ($auto instanceof CarInterface) {
echo $autos->getNaam();
}
}
?>
interface CarInterface {
public function getNaam();
public function getWielen();
}
class Volvo implements CarInterface {
public function getNaam() { return "Volvo"; }
public function getWielen() { return 4; }
}
class Citroen implements CarInterface {
public function getNaam() { return "Citroen"; }
public function getWielen() { return 4; }
}
$autos = [new Volvo(), new Citroen()];
foreach($autos as $auto) {
if ($auto instanceof CarInterface) {
echo $autos->getNaam();
}
}
?>
Waarom hier nu een interface? Je hebt een foreach loop. Maar die array kan alles bevatten, maar omdat we verwachten we dat getNaam() in de interface verplicht word kunnen we die veilig gebruiken.
Wat heel belangrijk is, en vooral voor jou in het bijzondere Ozzie, is dat je eerst zelf wat dingen uitprobeert. Dan worden de use-cases van een interface vanzelf duidelijk.
Gewijzigd op 07/03/2014 12:10:14 door - Raoul -
Dat hangt af van wie je het vraagt, over welke taal je het hebt etc.
Code (php)
1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
<?php
function consume(Consumable $substance) { ... } // function consume consumes a Consumable substance
function repair(Repairable $object) { ... } // function reparair repairs a Repairable substance
// versus
function consume(ConsumeInterface $substance) { ... } // function consume consumes a ConsumeInterface instance
function repair(RepairInterface $object) { ... } // function repair repairs a RepairInterface instance
// conclusie: *able vormt een beter over de tong rollende zin
?>
function consume(Consumable $substance) { ... } // function consume consumes a Consumable substance
function repair(Repairable $object) { ... } // function reparair repairs a Repairable substance
// versus
function consume(ConsumeInterface $substance) { ... } // function consume consumes a ConsumeInterface instance
function repair(RepairInterface $object) { ... } // function repair repairs a RepairInterface instance
// conclusie: *able vormt een beter over de tong rollende zin
?>
"Moet ik bijv. een losse getInterface, hasInterface en setInterface maken, en dan vervolgens een dataInterface die deze 3 interfaces extend?"
Als jij denkt dat je ze soms afzondelijk wilt implementeren...
Code (php)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<?php
// dit is bijvoorbeeld GEEN goed idee
class Meow {
private $object;
public function __construct(GetInteface $object) {
$this->object = $object;
}
public function doGet() {
// prima zolang Meow::setObject() niet is aangeroepen...
return $this->object->get();
}
public function doHas($mixed) {
// dit is onzin aangezien GetInterface alleen get() forceert
return $this->object->has($mixed);
}
public function doSet($mixed) {
// alleen correct als eerst Meow::setObject() is aangeroepen
return $this->object->set($mixed);
}
public function setObject(SetInterface $object) {
// Meow::$object was eerst een GetInterface instantie, nu wordt het een SetInterface instantie...
// WTF!?!
$this->object = $object;
}
}
?>
// dit is bijvoorbeeld GEEN goed idee
class Meow {
private $object;
public function __construct(GetInteface $object) {
$this->object = $object;
}
public function doGet() {
// prima zolang Meow::setObject() niet is aangeroepen...
return $this->object->get();
}
public function doHas($mixed) {
// dit is onzin aangezien GetInterface alleen get() forceert
return $this->object->has($mixed);
}
public function doSet($mixed) {
// alleen correct als eerst Meow::setObject() is aangeroepen
return $this->object->set($mixed);
}
public function setObject(SetInterface $object) {
// Meow::$object was eerst een GetInterface instantie, nu wordt het een SetInterface instantie...
// WTF!?!
$this->object = $object;
}
}
?>
Groepeer wat logisch is. Als het later blijkt dat opsplitsen logisch is kan het dan altijd nog. Ook is het niet veel werk omdat je alleen de interface aan hoeft te passen.
Als je het eerst opsplitst en later bedenkt dat het toch niet zo handig was is het meer moeite.
Gewijzigd op 07/03/2014 12:30:31 door Dos Moonen
Wat bedoel je Raoul?
@Dos: ik snap je voorbeeld niet helemaal :-s
Waarom dwing je bij setObject een SetInterface af?
>> Groepeer wat logisch is. Als het later blijkt dat opsplitsen logisch is kan het dan altijd nog. Ook is het niet veel werk omdat je alleen de interface aan hoeft te passen.
Zal ik onthouden. Goede tip.
Wat betekent bv. CarInterface, niet *able.
En dos, we hebben het hier uiteraard over PHP en niet over een andere taal.
Toevoeging op 07/03/2014 14:31:33:
Overigens... als in m'n interface een set method afdwing, kan ik dan in de class die die interface implement geen hints gebruiken? Dat lijkt namelijk niet te werken:
Code (php)
Dit gaat fout, omdat ik in de Foo class in de foo() method een instance van Bar afdwing. Maar dat gaat dus niet. Dat is niet handig... want stel we hebben bijv. een set() method, dan wil ik soms dat de value een array is, en soms dat het een object is. Hoe doe ik zoiets dan?
Gewijzigd op 07/03/2014 13:16:08 door Ozzie PHP
- Raoul - op 07/03/2014 13:01:36:
*Interface
Wat betekent bv. CarInterface, niet *able.
En dos, we hebben het hier uiteraard over PHP en niet over een andere taal.
Wat betekent bv. CarInterface, niet *able.
En dos, we hebben het hier uiteraard over PHP en niet over een andere taal.
Carable is ook geen goed engels woord...
Ozzie, het punt dat ik probeerde te maken was dat omdat iets kan (GetInterface, HasInterface en SetInterface opsplitsen) het geen goed idee hoeft te zijn.
In het voorbeeld ben ik uitgegaan van een "ik plits ze op omdat het kan" aanpak en ben daarna inconsequent te werk gegaan. Wat jammer genoeg nog wel eens voorkomt. Zeker als je met meerdere aan de zelfde code base werkt.
PHP laat je niet toe om methoden te overloaden zoals in bijvoorbeeld Java. In php moet de method signature 100% gelijk zijn. Ook een reden waarom ik een algemene SetInterface in php een vreemd idee vind.
Gewijzigd op 07/03/2014 14:43:44 door Dos Moonen
Ik heb je punt begrepen Dos. Alleen ik had nog een andere vraag. Stel dus dat je een setInterface hebt, of misschien wel een dataInterface waarvan de set() method een onderdeel is... stel dat ik dan in een child class een set() method implementeer, maar ik wil dat het 2e argument van een bepaald type is, hoe doe ik dat dan? Als je het voorbeeld in mijn vorige post ziet, dan gaat het mis. Ik wil dus wel zorgen dat de child class een set method heeft, maar in de child class zelf wil ik dan kunnen afdwingen wat voor type het argument moet zijn. Hoe krijg je zoiets voor elkaar?
Gooi een InvalidArgumentException.
Toevoeging op 07/03/2014 14:56:57:
Ik kan trouwens toch wel in iedere child class de set method overschrijven? Stel ik heb een data class met een set() method. De data class implement de setInterface. Als ik dan een child heb van de data class, kan die de set method van de data class dan overschrijven?
Gewijzigd op 07/03/2014 14:58:28 door Ozzie PHP
Lees aub de tutorial die ik je doorstuurde eens door. Daar ga je veel van leren. Je snapt wat een interface is, maar niet wanneer je het moet gebruiken.
Gewijzigd op 07/03/2014 15:20:03 door - Raoul -
Niet altijd: http://3v4l.org/urnWI
En zoals hier boven gezegd wordt, stap af van het idee van een SetInterface a.u.b. Ook in je voorbeelden/vragen.
Oké, inderdaad. Ik snap wat een interface is, maar ik weet inderdaad nog niet wanneer ik het moet gebruiken.
Kunnen jullie mij dan eens uitleggen waarom een setInterface geen goed idee is?
>> Niet altijd: http://3v4l.org/urnWI
Oké... maar zolang je de signature volgt dus wel?
Je moet niets. Maar het kan erg handig blijken. Als je programmeert op basis van interfaces in tegenstelling tot implementaties kun je fijn mock classes (http://en.wikipedia.org/wiki/Mock_object) gebruiken om unit tests te schrijven.
"Kunnen jullie mij dan eens uitleggen waarom een setInterface geen goed idee is?"
Een interface SetInterface/Setable binnen de global namespace is te vaag naar mijn idee. Binnen een namespace kan het een specifiekere betekenis hebben. Context is belangrijk.
Nogmaals: mijn mening is dat interfaces functionaliteit moet afdwingen. Het moet tot een zekere hoogte duidelijk worden wat die functionaliteit is. Met jouw SetInterface het het mij niet bepaald duidelijk wat de mogelijke gevolgen zijn van het setten, of wat het voor nut kan hebben.
Vergelijk dat met classes als Serializable, Countable en Iterator.
"Oké... maar zolang je de signature volgt dus wel?"
ja.
Code (php)
Als ik nu in een method doe: ObjectFetchInterface $repository Dan weet ik dat ik van dat object de 4 find* methods mag aanroepen, de persist method niet.
Oké, ik geloof dat ik het begin te begrijpen. Dank jullie wel!