[oop] abstracte class, invulling methods
Een vraag. Ik heb 3 classes die dezelfde abstracte class extenden.
Nu heb ik een verplcihte method foo(). Bij 2 van deze classes is de invulling van die method foo() precies hetzelfde. Bij de 3e class zit er een verschil in.
Wat is nu gebruikelijker?
optie 1)
We bouwen de overkoepelende foo() method van class 1 en 2 als abstracte method in de abstracte class. Class 1 en 2 gebruiken dus de foo() method uit de abstracte class. Class 3 krijgt z'n eigen foo() method.
optie 2)
In de abstracte class declareren we een lege abstracte foo() method en iedere class vult zelf deze method in. In class 1 en 2 is deze method exact hetzelfde.
Wat is juist?
Gewijzigd op 19/06/2014 23:43:18 door Ozzie PHP
>> Overigens is het beter te programmeren naar een interface dan naar een implementatie.
Bedoel je met interface in dit geval een abstracte method, of bedoel je een opzichzelf staande interface (in plaats van een abstracte class)?
Het 2e.
Oké... maar dat ben ik niet helemaal met je eens. Een op zichzelf staande interface (het 2e dus) gebruik je met name om naar bepaald "gedrag" te programmeren. Je kunt er mee aangeven dat een object zich op een bepaalde manier gedraagt (met de daarbij behorende methods). Een abstracte class is óók een interface, maar beschrijft een kant-en-klaar object.
een mercedes IS_EEN auto
een volvo IS_EEN auto
voor al het andere raad ik de interface aan. Waarom is de abstracte klasse niet altijd optimaal? Je kan het maar weinig gebruiken ( alleen in een IS_EEN relatie ) en verder is de code al geïmplementeerd waardoor je niet flexibel kan zijn met je implementatie.
p.s. overigens zijn jou voorbeeldjes vaag altijd waardoor je niet echt een tegenargument kan geven want wat is een foo? ^^
Gewijzigd op 20/06/2014 00:10:42 door Reshad F
Hehe... lol. Het maakt in dit geval niet uit wat foo is omdat de vraag daar niet over ging :)
In dit geval gaat het inderdaad om een IS_EEN relatie, dus dat klopt. Het ging me alleen dus even om hoe ik moet omgaan met een method die meerdere keren voorkomt maar niet overal. Maar daar had je me al antwoord op gegeven. Ik maak er dus een lege abstracte method van, en dan zal ik dus moeten accepteren dat die method in 2 classes op precies dezelfde manier wordt ingevuld.
Programmeren naar een interface doelt niet zozeer op het werkelijk gebruiken van een interface. Het doelt er meer op dat je niet moet programmeren naar 1 specifieke implementatie, maar liever het globaler aan moet pakken. Of dat globale nou een andere overkoepelende klasse is, een abstracte klasse of een interface maakt niet zo heel veel uit.
Overigens zou ik deze regel zeker niet altijd aan raden. Heb je bijv. een Factory klasse, dan is het programmeren naar een interface vrij zinloos.
Jouw voorbeeldje met mercedes en volvo zou ik overigens niet met inheritance oplossen (een van de meest misbruikte use cases van inheritance), maar met composition (bijv. het Strategy pattern).
Om op Ozzie's vraag goed te antwoorden hebben we eigenlijk meer context nodig. We zouden in dit geval namelijk voor 3 dingen kunnen kiezen: (1) inheritance, (2) composition of (3) copy/past. Voor alle 3 is wat te zeggen en alle 3 zijn ze juist, in hun eigen context. Welke er hier opgaat is dus volledig afhankelijk van die context.
Toevoeging op 20/06/2014 00:28:09:
P.S.
>> Ook interfaces moeten een IS_EEN relatie hebben.
Als je een "op zichzelf staande" interface bedoelt is dit niet helemaal waar.
Je kunt bijv. een interface Drivable hebben, die geimplementeerd wordt door een Car en een Bike class. Zowel een Car als een Bike !IS_EEN Drivable. Drivable beschrijft GEDRAG wat in de implementerende classes voorkomt.
Een slash achter een namespace?
Of een backslash?
Die slash of backslash hoort er niet in. Oplossing (4) weg ermee.
Een backslash, maar ik merk dat je niet helemaal begrepen hebt wat ik bedoel ;) Doet voor de vraagstelling overigens niet terzake.
Het doet in zoverre wel terzake dat je add_namespace() methode iets aan een namespace toevoegt dat niet in een namespace thuishoort. Of je zou de methode anders moeten noemen.
Als het om een kleine aanvulling op de abstracte klasse gaat, kun je de parent-methode aanroepen.
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
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
<?php
abstract class AbstractAutoloader
{
protected $Namespaces;
public function add_namespace($name, $path)
{
$this->Namespaces[$name] = $path;
}
}
class OrdinaryAutoloader extends AbstractAutoloader
{
// Blanco: implementeert AbstractAutoloader()
}
class SlashingAutoloader extends AbstractAutoloader
{
public function add_namespace($name, $path)
{
// Voeg die backslash toe ...
$name = rtrim($name, '\\') . '\\';
// ... en implementeer de rest
parent::add_namespace($name, $path);
}
}
?>
abstract class AbstractAutoloader
{
protected $Namespaces;
public function add_namespace($name, $path)
{
$this->Namespaces[$name] = $path;
}
}
class OrdinaryAutoloader extends AbstractAutoloader
{
// Blanco: implementeert AbstractAutoloader()
}
class SlashingAutoloader extends AbstractAutoloader
{
public function add_namespace($name, $path)
{
// Voeg die backslash toe ...
$name = rtrim($name, '\\') . '\\';
// ... en implementeer de rest
parent::add_namespace($name, $path);
}
}
?>
Hehe... lol :)
De parent aanroepen zou kunnen, maar in jouw voorbeeld zou ik denk ik gewoon die regel code uit de parent in de method zelf zetten. Scheelt toch weer een method-call.
Dat zou ik niet doen: je wilt de methode in de abstracte klasse kunnen herschrijven of uitbreiden. Dat het nu maar één regel is, wil niet zeggen dat dat in de toekomst ook zo is. In de gekloonde methode wil je daarom liefst alleen de verschillen met het origineel vastleggen.
Mja... dat is een persoonlijke keuze. Je kunt het ook zien dat je nu de taak van 1 method gaat spreiden over 2 methods. Als er daadwerkelijk complexe logica wordt uitgevoerd, dan zou ik het nog een ander verhaal vinden. Maar om enkel iets te setten een andere method aanroepen... hmmm, kweet het niet. Je kunt het ook andersom zien. Je past de parent method aan, en creëert daarmee mogelijk zonder dat je het in de gaten hebt een probleem voor de SlashingAutoloader. Het werkt dus 2 kanten op.
DRY, look it up
Als je dergelijke verschillen met strings voor namen en paden vaker verwacht, zou je ook nog late static bindings kunnen gebruiken.
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
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
<?php
abstract class AbstractAutoloader
{
const NAMESPACE_PREFIX = '';
const NAMESPACE_POSTFIX = '';
protected $Namespaces;
public function add_namespace($name, $path)
{
$name = static::NAMESPACE_PREFIX . $name . static::NAMESPACE_POSTFIX;
$this->Namespaces[$name] = $path;
}
}
class OrdinaryAutoloader extends AbstractAutoloader
{
// Blanco: implementeert AbstractAutoloader() volledig
}
class SlashingAutoloader extends AbstractAutoloader
{
// Implementeert maar één verschil
const NAMESPACE_POSTFIX = '\\';
}
?>
abstract class AbstractAutoloader
{
const NAMESPACE_PREFIX = '';
const NAMESPACE_POSTFIX = '';
protected $Namespaces;
public function add_namespace($name, $path)
{
$name = static::NAMESPACE_PREFIX . $name . static::NAMESPACE_POSTFIX;
$this->Namespaces[$name] = $path;
}
}
class OrdinaryAutoloader extends AbstractAutoloader
{
// Blanco: implementeert AbstractAutoloader() volledig
}
class SlashingAutoloader extends AbstractAutoloader
{
// Implementeert maar één verschil
const NAMESPACE_POSTFIX = '\\';
}
?>
Ben jij het dan NIET eens met wat Reshad hierboven zegt?
Reshad F op 19/06/2014 23:46:45:
je gebruikt die abstractie wanneer je weet dat ELKE klas die het overneemt de zelfde implementatie gebruikt van die methode. wanneer je dit dus verschillend hebt dan kan je het beter leeg laten. ...
@Ward:
Daar ga ik eens over nadenken. Dat is wellicht een goede optie en wat minder "gevaarlijk" wat betreft het spreiden van code.
Alleen heb je hier een marginaal verschil in slechts één afgeleide klasse. Zoals ik daarom zei: "Als het om een kleine aanvulling op de abstracte klasse gaat ...". Daarom implementeer je uitsluitend die kleine aanvulling in de afgeleide methode en laat je het standaardprototype via parent:: de rest doen.
Maar is dat niet een "risico" dan? Als ik de method in de parent class invul is het geen abstracte method meer. Dan komt er een dag (enigszins geneuzel dat wel, maar het gaat even om het idee) dat ik besluit om in de parent class een slash toe te voegen aan de namespace. Het gevolg: die afwijkende class krijgt ineens 2 slashes achter een namespace. Dat is (denk ik) toch ook een risico? Is het dan niet veiliger om de method abstract te maken en de invulling aan de childs over te laten, waarbij je moet accepteren dat in 2 methods hetzelfde gebeurt?
$name = rtrim($name, '\\') . '\\';
Risico = kans × effect. Als de abstract class wordt verbeterd en dat niet met copy/paste wordt overgenomen, heeft dat ingrijpendere gevolgen. De kans is klein, maar het effect is groot. Daarom formaliseer je die copy/paste met parent::add_namespace().