public function foo () {
if ($this->locked) throw Exception('class is locked');
}
public function is_locked() {
return $this->locked;
}
}
?>
Toen bedacht ik vandaag... is het wel correct dat foo() rechtstreeks de locked property aanspreekt. Ligt de verantwoordelijkheid of de class gelockt is niet bij is_locked()? Stel bijv. dat in de toekomst het niet alleen van de property locked afhangt, maar ook nog van een andere variabele, dan werkt de code niet meer zoals het zou moeten. Zou het daarom niet zo moeten, vroeg ik me af?
<?php
class Foo {
private $locked;
public function foo () {
if ($this->is_locked()) throw Exception('class is locked');
}
public function is_locked() {
return $this->locked;
}
}
?>
Op zich zou je denken dat dit beter is, omdat nu daadwerkelijk is_locked bepaalt of de class op slot zit.
Maar... als je die lijn doortrekt, dan zou je dus ook niet dit krijgen...
<?php
class Data {
private $data;
public function foo() {
if (isset($this->data['foo'])) // doe iets;
}
public function has($id) {
return isset($this->data[$id]);
}
}
?>
Maar dit...
<?php
class Data {
private $data;
public function foo() {
if ($this->has('foo')) // doe iets;
}
public function has($id) {
return isset($this->data[$id]);
}
}
?>
Immers, de verantwoordelijkheid of iets bestaat hebben we bij has() neergelegd.
Trek je deze lijn nog verder door, dan zou je niet dit krijgen...
<?php
class Data {
private $data;
public function count() {
return count($this->data);
}
public function foo() {
if (count($this->data) > 3) // doe iets;
}
}
?>
Maar dit...
<?php
class Data {
private $data;
public function count() {
return count($this->data);
}
public function foo() {
if ($this->count() > 3) // doe iets;
}
}
?>
Maar sla je dan niet te ver door vraag ik me af? Wie kan er iets zinnigs over zeggen?
Jij gaat een Bert object gebruiken in je library? Levens voorbeelden zijn heel slecht om te bedenken als je over OO design nadenkt. Het is leuk om heel in het begin, toen je net OO ging leren, er even in te komen. Dat ligt al ver achter je.
Het moeilijke van voorbeelden is dat je deze nooit 100% kunt overplaatsen. Je kan een voorbeeld bijv. gebruiken om deelvraag 1 van onderwerp A te verklaren, maar dat zelfde voorbeeld kan totaal niet opgaan voor deelvraag 2. Voorbeeldje: De deeltjes gebruiken andere deeltjes om krachten aan elkaar door te geven. Een afstotende kracht kun je mooi uitleggen door 2 mensen op ijs een basketbal te laten overgooien, ze zullen dan allebei uit elkaar gaan. Een aantrekkende kracht is op deze manier echter helemaal niet uit te leggen.
Een voorbeeld moet je dus alleen gebruiken als je 100% zeker bent van het onderwerp waar je over praat. Anders kan je hem misschien wil op verkeerde momenten gebruiken. Je gaat dan een onderwerp verzinnen om jouw mening te ondersteunen, niet om de waarheid te verkrijgen.
Het is duidelijk dat je nooit bij de Belastingdienst hebt gewerkt, Ozzie. ;-)
Daar zou je in situatie 3 en 4 een Bert hebben om te controleren of de deur op slot zit, en een Sjaak om te bepalen of er geen giftige dampen in de loods hangen.
Anders gezegd: een functie die is_locked heet, moet alleen kijken of de deur op slot zit. Wil je ook controleren op rondwalmend mosterdgas, dan moet je daar een andere functie voor bedenken. is_safe_to_enter() of zo.
Uiteraard staat het je vrij om een overkoepelend controleorgaan is_accessible() te maken die achtereenvolgens is_locked() en is_safe_to_enter() uitvoert. Maar in dat geval zou wel alle toegang via die is_accessible() moeten lopen. Bij de Belastingdienst kom je tenslotte ook niet bij Bert voordat de receptie je heeft doorgelaten.
Toevoeging op 29/03/2014 23:37:00:
Je zou natuurlijk ook een briefje op de deur kunnen hangen waarin gewaarschuwd wordt voor gevaarlijke gassen...
>> Je gaat dan een onderwerp verzinnen om jouw mening te ondersteunen, niet om de waarheid te verkrijgen.
Wouter, ik vind het vervelend/lastig als je dit soort suggestieve opmerkingen maakt. Ik weet dan niet of je me wil helpen of juist niet.
Ik zit geen onderwerp te verzinnen om mijn mening te ondersteunen. Ik stel een vraag, die blijkbaar moeilijk is uit te leggen en daarom probeer ik analogieën te bedenken om het probleem duidelijk te maken.
@Willem:
Kijk, daar kan ik wat mee. Nu vertel je iets wat me een stapje verder brengt. Als ik je dus goed begrijp, zeg jij eigenlijk dat (in dit geval) de locked parameter het resultaat is van één taak, en dat je die locked parameter dus gerust vanuit een andere functie mag raadplegen. Correct?
Kijk, daar kan ik wat mee. Nu vertel je iets wat me een stapje verder brengt. Als ik je dus goed begrijp, zeg jij eigenlijk dat (in dit geval) de locked parameter het resultaat is van één taak, en dat je die locked parameter dus gerust vanuit een andere functie mag raadplegen. Correct?
Ja, met als kanttekening dat je eigenlijk per situatie moet bekijken of dat handig/verstandig is.
Om maar weer een beetje door te borduren op het voorbeeld met de loods:
Je zou een class kunnen zien als een team/afdeling. De functies zijn medewerkers/collega's.
Binnen een team weet je vaak ook wel waar je collega's mee bezig zijn. Vooral in kleinere bedrijven is het niet ongebruikelijk dat iemand zelf naar de loods loopt om er iets in te stoppen of uit te halen. Als er een extra veiligheidscheck gedaan moet worden in verband met de mogelijke aanwezigheid van gifgas, dan zal er een memo worden rondgestuurd aan het team waarin de procedure wordt uitgelegd. Iemand die in de loods moet zijn, weet dan dat als hij de deur van slot heeft gehaald, hij ook even moet kijken of het kanariepietje niet met zijn pootjes omhoog ligt (oude mijnwerkerstruc). En als iemand het toch niet durft, kan hij altijd aan een collega vragen of die meeloopt.
Oftewel: binnen een class hoeft het geen probleem te zijn als methods rechtstreeks van private properties gebruik maken. Dat betekent dan wel, dat als er ooit iets wordt gewijzigd in de procedures, alle methods nagekeken/gewijzigd moeten worden. Bij kleinere classes is dat nog wel te overzien. En het is natuurlijk ook geen probleem als een method wél gebruik maakt van een andere method om die properties te bewerken.
Bij grote bedrijven zul je een Bert hebben die de loods beheert. Als een medewerker in die loods moet zijn, moet hij zich altijd eerst bij Bert melden. Hij haalt dan de spullen uit de loods die de medewerker nodig heeft (of stopt erin wat de medewerker meebrengt). De medewerker zelf komt in principe nooit in de loods, maar blijft aan het bureau van Bert staan tot de spullen zijn opgehaald.
Dit komt overeen met een class waarvan de methods nooit rechtstreeks de private properties benaderen die onder verantwoordelijkheid van een andere method vallen. In plaats daarvan vragen ze de informatie intern op via de verantwoordelijke method.
Het aanroepen van zo'n method brengt overhead met zich mee (en het benaderen van de loods via Bert ook, want je moet als medewerker eerst aan Bert vertellen wat hij moet ophalen). Wanneer een class wat groter wordt, is die overhead vaak goed te verdedigen omdat het nog veel meer kost om fouten te corrigeren die zijn ontstaan door buiten de procedures om te werken.
Een ander voordeel van toegang via een centrale Bert() is wanneer Bert meerdere handelingen moet uitvoeren: slot controleren, gasmetingen uitvoeren, etc. Je hoeft dan alleen Bert maar te vertellen wat er is gewijzigd en de andere medewerkers/methods hoeven daar geen weet van te hebben. Een abstractielaag dus.
Overigens is dat geen garantie dat er nooit iets hoeft te wijzigen. Het kan zijn dat de directie bepaalt dat medewerkers niet meer rechtstreeks naar Bert mogen, maar eerst toestemming moeten hebben van hun teamleider (of het zelfs de teamleider moeten laten doen). Voor een class betekent dat, dat de methods die Bert() aanroepen toch herschreven moeten worden.
Als vuistregel zou ik dus zeggen: wanneer je class niet al te groot is, laat dan de methods zelf aanrommelen met de properties. Heb je een grotere class, doe dat dan niet.
Je zou nu als argument kunnen geven dat je van tevoren niet kan bepalen of een class altijd klein zal blijven, waardoor je dus de methods nooit rechtstreeks de properties laat benaderen. Echter, als een class die klein is opgezet in een later stadium flink groeit, loont het waarschijnlijk de moeite om de boel een keer te redesignen.
Wat versta je onder een grote class, over hoeveel regels praat je dan (ongeveer)?
Geen flauw idee, ik OO niet ;-)
Maar goed, dit soort issues heb je ook in procedurele code. Die grens ligt denk ik ook niet scherp en is niet alleen afhankelijk van het aantal regels code, maar ook van het aantal methods/functies, de complexiteit van de interactie (komt 1 aanroep van Bert overeen met 1 statement, of zijn het er misschien wel 10?), de dichtheid van de code (hoeveel gebeurt er in 1 regel?) en vooral ook je eigen referentiekader. Sommige programmeurs vinden 250 regels code al veel en andere vinden 5000 regels nog klein.
Als ik naar mezelf kijk, dan schat ik in dat ik de interne communicatie in mijn software wat formeler ga aanpakken vanaf zo'n 1000 regels code (ruwweg 16 kantjes A4).
Nou, ik zal niet zeggen dat ik nooit aan OO doe (ik vind dat trouwens een beetje een Teletubbiekreet: Oh, oh...) maar het is inderdaad niet het eerste instrument waar ik naar grijp als ik iets maak. Dat kan ook te maken hebben met het feit dat ik vaak software maak waar ik OO minder bij vind passen. Het is best mogelijk die software in OO te schrijven op zo'n manier dat het goed werkt, maar de manier waarop het is geïmplementeerd is dan wellicht niet de meest logische (je kan best een schroef in de muur krijgen met een hamer, maar of het logisch is?). Als vuistregel hou ik altijd aan: als je data niet gemodelleerd kan worden op een relationele database, dan moet je geen OO gebruiken.
Wat ik een van de grote nadelen aan OO vind, is de neiging om je software te gaan over-engineeren, wat weer kan leiden tot inefficiënte code. Vooral bij programma's die niet groter zijn dan een paar honderd regels is het toepassen van OO vaak vergelijkbaar met het gebruiken van een kanon om op een mug te schieten.
Dat wil overigens niet zeggen dat als je geen OO gebruikt je je code gewoon onder elkaar pleurt (zoals jij lijkt te suggereren). Er zijn meer manieren dan alleen OO om je code te structureren. ;-)
Daarnaast zijn er situaties waar OO echt niet het juiste vehikel is. Niet alle datatypes voldoen bijvoorbeeld aan het substitutiepricipe van Liskov, en het is dan best lastig om in een OO-taal iets te schrijven waarmee je jezelf als programmeur niet volledig belachelijk maakt.
Misschien moet ik dat toelichten met een voorbeeld. ;-) Stel, je hebt een class 'vogel' met subclasses als 'duif', 'mus' en 'spreeuw'. Per definitie erft een subclass alle eigenschappen van de superclass. Omdat alle vogels kunnen vliegen, heeft de superclass 'vogel' dus een method 'vliegen()'.
Tsja, en vervolgens wil je subclasses 'pinguïn' en 'struisvogel' toevoegen... Die erven de method vliegen() terwijl die helemaal niet van toepassing is.
Nu zou je nog kunnen zeggen dat dat betekent dat je de class 'vogel' verkeerd hebt gemodelleerd en dat je eigenlijk eerst subclasses 'vliegvogels', 'loopvogels' en 'vreemdevogels' had moeten bedenken, maar dat was in het begin misschien helemaal niet relevant. Kom je weer op het punt over-engineeren: wat je ook bedenkt, je kunt altijd wel iets bedenken om het nóg abstracter/uitgebreider/ingewikkelder te maken. Bovendien maak je een model van de werkelijkheid, en die werkelijkheid kan ook nog veranderen. Je kan wel proberen dat allemaal mee te modelleren, maar ergens moet je een grens stellen, anders krijg je op een gegeven moment de situatie dat je voor een simpele write('Hello world') 75 duizend regels code nodig hebt.
Haha... je gebruikt wel een bijzondere uitleg om te verklaren waarom je geen voorstander van OO bent. Volgens mij kun je de nadelen die je noemt met bepaalde patterns ondervangen. Maar ieder z'n ding uiteraard :)
>> Dat wil overigens niet zeggen dat als je geen OO gebruikt je je code gewoon onder elkaar pleurt (zoals jij lijkt te suggereren). Er zijn meer manieren dan alleen OO om je code te structureren. ;-)
Wat doe je dan? Gewoon heel veel bestanden en telkens includes gebruiken???
>> Wat doe je dan? Gewoon heel veel bestanden en telkens includes gebruiken???
Er is ook nog iets als functioneel programmeren. En ook procedureel kun je met goed nadenken tot goede code omzetten. Procedureel !== Italiaanse spaghetti code.