mee eens? (leesvoer)
Ik kwam dit artikel tegen. Strekking: gebruik getters/setters in plaats van public properties. Er wordt grofweg gesteld dat je geen public properties moet gebruiken, omdat je daar eventuele set methods mee kunt overrulen, en omdat het makkelijker is om je systeem te onderhouden via getters en setters. Mee eens... of te kort door de bocht?
Hier het artikel:
http://blog.everymansoftware.com/2012/03/getset-methods-vs-public-properties.html
Voor de mensen die aan OOP doen, ik zou het leuk vinden als jullie een reactie geven. Werken jullie zelf met public properties of alleen met getters en setters?
Gewijzigd op 17/03/2013 21:29:23 door Ozzie PHP
- http://www.phphulp.nl/php/forum/topic/verantwoordelijkheid-get/89204/
- http://www.phphulp.nl/php/forum/topic/getters-setters-public-properties/89713/
- http://www.phphulp.nl/php/forum/topic/dollarclassgetiets-vs-dollarclassiets/89085/
- http://www.phphulp.nl/php/forum/topic/magic-get-set/88985/
- http://www.phphulp.nl/php/forum/topic/setten-in-construct-of-in-aparte-functie/88454/
- http://www.phphulp.nl/php/forum/topic/dubbelop-of-niet/89402/last/
Er zit zeker een stukje overlap in.
Ik hoop echter dat ik nu goede informatie heb gevonden waaruit EENDUIDIG blijkt of je wel of geen getters/setters moet gebruiken. Ik vind de argumenten in het artikel wel behoorlijk overtuigend, maar ben benieuwd of de OOP gebruikers het er mee eens zijn???
(Je hebt gelijk hoor dat ik het al vaak over dit onderwerp heb gehad. Ik dacht ook dat ik er 100% uit was... totdat ik gisteren door iemand aan het twijfelen ben gebracht omdat het direct aanspreken van public properties sneller is dan het gebruik van getters. En als het om snelheid gaat... je kent me inmiddels :) Daar ben ik gevoelig voor. En nu wil ik dus nog 1x voor mezelf duidelijk hebben wat ik moet doen. Gezien het snelheidsvoordeel neig ik naar public properties, maar als ik de argumenten in het artikel lees dan neig ik weer naar getters. Ik wil nu voor eens en voor altijd de knoop doorhakken... heeelluup!)
Gewijzigd op 17/03/2013 21:49:11 door Ozzie PHP
Ozzie PHP op 17/03/2013 21:47:59:
En als het om snelheid gaat... je kent me inmiddels :) Daar ben ik gevoelig voor.
Als je snelheid zo belangrijk vindt, waarom gebruik je dan OO? ;-)
OO is bedoeld voor abstractie, niet om snelle programma's te schrijven. Het argument dat je public properties moet gebruiken vanwege de snelheid is dus bedacht door iemand die het object 'bel' heeft horen luiden, maar niet weet waar de property 'klepel' hangt. Of zo. Ben slecht in spreekwoorden.
Aan de andere kant, als je object voornamelijk bestaat uit getters en setters, dan klopt je ontwerp ook niet; dan doe je alleen maar aan information hiding, en daar is OO een beetje een zwaar middel voor. Als ik het me goed herinner, móchten we tijdens mijn studie niet eens methods maken die alleen maar een get of set van een property deden.
Gewijzigd op 17/03/2013 23:48:21 door Willem vp
Dankjewel voor je reactie! Blij dat er iemand reageert :-)
Nee, klopt dat OO niet bedoeld is voor snelheid. Maar ik vind OO wel een prettige manier van werken, en ik wil dan voor mezelf de meeste snelheid eruit halen die erin zit.
Kun je eens uitleggen, of misschien een voorbeeld geven wat je bedoelt met jouw laatste opmerking? Dat snap ik niet helemaal.
Willem vp op 17/03/2013 23:45:13:
Aan de andere kant, als je object voornamelijk bestaat uit getters en setters, dan klopt je ontwerp ook niet; dan doe je alleen maar aan information hiding, en daar is OO een beetje een zwaar middel voor. Als ik het me goed herinner, móchten we tijdens mijn studie niet eens methods maken die alleen maar een get of set van een property deden.
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
36
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
36
<?php
class Rekening {
private $saldo = 1000;
private $bedrag;
public function neemOp($bedrag) {
$this->bedrag = $bedrag;
if($bedrag < $this->saldo) {
$this->saldo -= $bedrag;
return sprintf('u heeft nog %01.2f euro op de bank', $this->saldo);
}
else {
return sprintf('U heeft maar %01.2f euro op de bank', $this->saldo);
}
}
public function stort($bedrag) {
// hier je controle etc..
}
}
$neemgeld = new Rekening();
echo $neemgeld->neemOp(80);
?>
class Rekening {
private $saldo = 1000;
private $bedrag;
public function neemOp($bedrag) {
$this->bedrag = $bedrag;
if($bedrag < $this->saldo) {
$this->saldo -= $bedrag;
return sprintf('u heeft nog %01.2f euro op de bank', $this->saldo);
}
else {
return sprintf('U heeft maar %01.2f euro op de bank', $this->saldo);
}
}
public function stort($bedrag) {
// hier je controle etc..
}
}
$neemgeld = new Rekening();
echo $neemgeld->neemOp(80);
?>
Hoe zou jij het bovenstaande willen doen zonder een getter en een setter? ( op een veilige manier met controles )
Ik zou je gewoon aanraden alles private te houden. Het kan wel zo zijn dat je een paar regels meer code hebt maar het uiteindelijke doel van OOP is dat je straks gewoon kan zeggen
zonder na te denken over de rest van de klasse. ( en natuurlijk het hergebruik ervan )
als je OOP programmeert moet je naar mijn mening eerder kijken naar de manier van het programmeren ( dus OO ) dan te kijken naar de performance. als je het echt snel wilt hebben moet je procedurele code schrijven.
Als je naar jouw voorbeeld kijkt en $saldo zou een public property zijn, dan zou je bijv. dit kunnen doen:
$saldo = $this->saldo;
Als $saldo een private property is, zou je het zo moeten doen:
$saldo = $this->getSaldo();
Het 1e voorbeeld is sneller, maar het 2e voorbeeld is veiliger. In het 1e voorbeeld zou ik namelijk makkelijk het saldo kunnen veranderen.
Ik denk dat het inderdaad beter is om getters/setters te gebruiken in plaats van public properties. Ik vind het toch wat te veel risico.
Gewijzigd op 18/03/2013 00:40:39 door Ozzie PHP
Ozzie PHP op 17/03/2013 23:50:26:
Nee, klopt dat OO niet bedoeld is voor snelheid. Maar ik vind OO wel een prettige manier van werken, en ik wil dan voor mezelf de meeste snelheid eruit halen die erin zit.
Nu komen we op het gebied van de micro-optimalisatie, maar ik geloof dat ik snel moet bukken als ik dat woord noem. ;-)
Quote:
Kun je eens uitleggen, of misschien een voorbeeld geven wat je bedoelt met jouw laatste opmerking? Dat snap ik niet helemaal.
Pff... nu moet ik even een jaar of 20 terug in mijn geheugen, en ik heb er soms al moeite mee om 20 minuten terug te gaan... Maar goed, laat ik een poging wagen:
Laten we eerst vaststellen wat een object is: een zo natuurgetrouw mogelijke representatie van een al dan niet tastbaar begrip uit de werkelijkheid. De inhoud van een object kan in principe niet rechtstreeks benaderd worden; voor alle manipulaties op de inhoud moet gebruik gemaakt worden van messages. Een message is een boodschap die naar een object kan worden gestuurd en tot een bepaalde actie van dat object leidt.
Je zou kunnen beargumenteren dat het simpelweg getten of setten van een property eigenlijk geen actie van het object veroorzaakt. Er moet meer achter zitten.
Neem bijvoorbeeld als object een 4-kleurenpen. Als je daar een method set_color hebt met als parameter de kleur dan zal een set_color(zwart) niet alleen de zwarte stift uitschuiven. Hij zal eerst moeten kijken of er niet al een andere stift is uitgeschoven. Zo ja, dan moet die worden ingetrokken voordat de zwarte stift uitgeschoven kan worden.
Dat is ook meteen de reden dat public properties eigenlijk 'not done' zijn. Het object raakt dan het overzicht/de controle kwijt.
Overigens denk ik dat er altijd wel redenen te bedenken zijn om toch een 'simpele' get- of set-method te maken. In bovenstaand voorbeeld zou ik me goed kunnen indenken dat ik wil weten wat de huidige kleur is waarmee de pen schrijft. Aan de andere kant: wat heeft het voor nut om dat te weten, want als ik op basis van die wetenschap iets wil doen, moet ik dat toch via een method regelen. De logica om iets met die waarde te doen zou dus ín het object moeten zitten, en niet erbuiten.
Dat was volgens mij zo ongeveer de gedachte achter het niet mogen gebruiken van 'simpele' getters en setters. Bovendien werd je daardoor gedwongen om verder door te denken over het ontwerp van je classes en objecten.
Willem vp op 18/03/2013 00:43:20:
Nu komen we op het gebied van de micro-optimalisatie, maar ik geloof dat ik snel moet bukken als ik dat woord noem. ;-)
Hehehe... +1
Oké... ik probeer jouw gedachtengang met de pen hierboven een beetje te volgen, maar ik begrijp het nog steeds niet helemaal. Stel dat jouw pen verkrijgbaar is in 5 verschillende merken. Nu wil ik het merk weten van de huidige pen. We willen geen public properties gebruiken, dus we hanteren een method getBrand().
Het enige wat die getBrand() doet is dit:
Dit is dus een simpele getter. Maar waarom zou dat dan niet goed zijn? En hoe zou je anders het merk moeten opvragen? (Het enige alternatief is dan een public property, maar dat is ook niet goed toch?)
Wat voor nut heeft het om te weten wat voor merk pen je hebt? Als je ermee wilt schrijven, maakt het niet uit of het een Bic of een Parker is, of desnoods het huismerk van de Lidl.
Nu ga ik nog een stap verder: ik zou me kunnen voorstellen dat een pen van een bepaald merk properties heeft die pennen van een ander merk niet hebben. In dat geval zou je misschien zelfs subclasses Bicpen, Parkerpen en Lidlpen moeten maken. ;-)
Property get/set syntax RFC die een andere PHP-syntaxis aanbeveelt en ook uitlegt waarom. Een property wordt hierbij gedefinieerd inclusief de eigen setter en getter.
Een eigenschap "is iets" en een methode "doet iets". Dat maakt een aanroep $time->Hours voor een eigenschap in mijn ogen netter en logischer dan de $time->getHours() die we nu zouden gebruiken.
Leesvoer: er is een Code (php)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?php
class TimePeriod
{
private $seconds;
// Properties are implemented using the "property" keyword, just like functions/methods use the "function" keyword
public property Hours
{
get { return $this->seconds / 3600; }
set { $this->seconds = $value * 3600; } // The variable $value holds the incoming value to be "set"
}
};
// Accessing the property is the same as accessing a class member
$time = new TimePeriod();
$time->Hours = 12; // Stored as 43200
echo $time->Hours; // Outputs 12
?>
class TimePeriod
{
private $seconds;
// Properties are implemented using the "property" keyword, just like functions/methods use the "function" keyword
public property Hours
{
get { return $this->seconds / 3600; }
set { $this->seconds = $value * 3600; } // The variable $value holds the incoming value to be "set"
}
};
// Accessing the property is the same as accessing a class member
$time = new TimePeriod();
$time->Hours = 12; // Stored as 43200
echo $time->Hours; // Outputs 12
?>
Een eigenschap "is iets" en een methode "doet iets". Dat maakt een aanroep $time->Hours voor een eigenschap in mijn ogen netter en logischer dan de $time->getHours() die we nu zouden gebruiken.
Willem vp op 18/03/2013 08:25:32:
En dan komen we weer terug bij de vraag "wat zou je met die informatie willen doen?"
Wat voor nut heeft het om te weten wat voor merk pen je hebt?
Wat voor nut heeft het om te weten wat voor merk pen je hebt?
Ja, maar dat is toch een vreemde vraag die je nu stelt? Laten we dan in plaats van een pen een auto nemen. Stel we zitten op de website van een autogarage en ik wil een auto kopen van een bepaald merk. Dan moet een class toch gewoon het merk van de auto kunnen teruggeven?
Ozzie PHP op 18/03/2013 12:51:08:
Ozzie, deze RFC is een voorstel voor een verbetering van PHP. Ik heb hem hier aangehaald om te laten zien dat je niet de enige bent die vraagtekens zet bij de huidige PHP-implementatie van getters en setters. En om te laten zien hoe het beter zou kunnen.Kan iemand het voorbeeld van Ward hierboven eens checken? Bij mij werkt het namelijk niet. Bij jullie wel??
Snap jij overigens wat Willem bedoelt met het opvragen van een merk?
Willem bedoelt waarschijnlijk dat een klasse met de eigenschappen Foo en Bar niet automatisch een setFoo() met getFoo() en een setBar() met een getBar() heeft. De vanzelfsprekendheid van dit automatisme is aanvechtbaar; de methoden moeten hun bestaansrecht ontlenen aan iets waarvoor je ze kunt/moet gebruiken. Je ontwerpt een model met een reden; je bouwt een klasse met een doel.
Die complexiteit proef je als je een setColor() en een setBrand() hebt maar een bepaald merk pen niet in alle kleuren verkrijgbaar is. Naïeve setters en getters zijn dan slecht bruikbaar. En zou je een class ParkerPen extends Pen gebruiken, dan gooien ze mogelijk roet in het eten via inheritance.
Dat brengt je dus bij de vraag welk doel zo'n getBrand() nu eigenlijk dient. Is het een display-methode voor een echo $object->getBrand()? Of heb je de methode voor iets anders ingebouwd, bijvoorbeeld voor het intern sturen van je $this->setColor(...)? Dát is de hamvraag: "Wat zou je met die informatie willen doen?"
Willem stelde het volgende: "Als ik het me goed herinner, móchten we tijdens mijn studie niet eens methods maken die alleen maar een get of set van een property deden."
En hier ging mijn vraag dus precies over. Bovenstaande opmerking suggereert dat je geen getters mag gebruiken die selchts een property teruggeven. En daar ging mijn vraag dus over... hoezo zou dat niet mogen? Dat getBrand() is hier slechts een voorbeeldje. Geen idee of je dat in de praktijk nodig hebt. Maar goed... laten we bijvoorbeeld even terugpakken op het voorbeeld van Reshad. Stel een gebruiker vraagt z'n banksaldo op. Dit banksaldo is door een private setter geset, en zal moeten worden opgehaald door een public getter. Mee eens? (Uiteraard kun je van het saldo een public property maken, maar dat lijkt me niet wenselijk omdat het saldo dan van buiten de class kan worden gewijzigd.) Als ik nu het saldo opvraag, dan is het enige wat die method doet, de waarde van de property retourneren. En volgens de opmerking van Willem zou dat dus niet goed zijn. Dat is dus waar mijn "verwarring" vandaan komt. Waarom zou een getter die een property retourneert per definitie niet goed zijn?
Ozzie PHP op 18/03/2013 14:24:21:
Willem stelde het volgende: "Als ik het me goed herinner, móchten we tijdens mijn studie niet eens methods maken die alleen maar een get of set van een property deden."
Je kunt daarvoor verschillende redenen aanvoeren.
• Een best practice is per definitie nooit een bad practice en al helemaal geen evil practice. Als je een private $PinCode openzet met een public function getPinCode(), barst iedereen spontaan in tranen uit. Maar dan moet je ook niet luid gaan staan applaudisseren als iemand een willekeurige private $Foo op de automatische piloot begint met een public function getFoo(). Er is niets vanzelfsprekends aan dit prototype:
Code (php)
• Je kunt zichtbaarheid niet meer goed regelen. Een private $Foo is niet meer volledig private als deze een public function setFoo() en een public function getFoo() heeft. Private en public mengen niet goed als die voor een eigenschap anders zijn dan voor methoden die uitsluitend aangrijpen op die eigenschap.
• Je omzeilt de standaardwerking van PHP's magische methoden __get() en __set(). Dat is overigens een van de bijeffecten die de RFC hoopt op te lossen.
• Het is nadelig voor de performance, omdat een naïeve methode slechts interne standaardfunctionaliteit nabootst. Een methode die niets toevoegt, heeft geen toegevoegde waarde. Of anders gezegd: je programmeert iets dat er al is en dat is wel het laatste dat je bij OOP wilt doen.
• Met naïeve setters en getters adresseer je een eigenschap in wezen nog steeds rechtstreeks of "blind" en dat wil je vaak helemaal niet. De klasse wordt met naïeve setters en getters inhoudelijk niet wezenlijk anders of beter gebruikt dan wanneer je een public $Foo zou hebben: alleen de syntaxis verandert.
• Op het niveau van een applicatie werkt het nodeloos omslachtig gebruik van de klasse als datacontainer in de hand. Als je al $x = 'y' weet, hoef je dit niet met $object->setFoo($x) op te slaan om het later met $z = $object->getFoo() op te vragen. De setFoo() biedt je slechts schijnzekerheid als het een naïeve setter is: er lijkt iets met $x te gebeuren maar dat is niet zo.
Maar dan hebben we het dus uitdrukkelijk alleen over naïeve setters en getters. Zodra een methode iets met een eigenschap doet, is deze niet langer naïef. Een getter die een property retourneert, is dus niet per definitie slecht!
Magic getters en setters zou ik zelf overigens nooit gebruiken omdat ze enorm traag zijn (doe maar eens een benchmark).
Als dat RFC verhaal doorgaat dan wordt het een stuk makkelijker, maar zal dat ooit gebeuren?
Quote:
Als dat RFC verhaal doorgaat dan wordt het een stuk makkelijker, maar zal dat ooit gebeuren?
Ja, hij stond op het programma voor PHP5.5, maar is toch wat vertraagd, grote kans dat ie in PHP5.6 zit.
Je moet het misschien praktischer aanvaren. Zo'n getBrand() schrijf je pas als je de methode ergens voor nodig hebt en nooit enkel en alleen omdat je class een property $Brand heeft. Dan weet je ook precies waartoe die methode dient en hoe de methode gebruikt mag/moet worden.
Omdat het ook leuk is om te kijken hoe andere talen dan de standaard C talen het aanpakken: Ruby gebruikt de zogenaamde accessors. Hiermee kun je aangeven of een property alleen leesbaar is (of alleen schrijfbaar) of allebei. Er wordt dan zo'n standaard getter/setter gemaakt (merk op dat alle properties private zijn in ruby):
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
36
37
38
39
40
41
42
43
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
36
37
38
39
40
41
42
43
class Person
# maakt getter aan
attr_reader :name, :gender
def initialize
@male_names ['jan', 'ben', 'klaas', ...]
@female_names ['nienke', ...]
end
# setter voor naam
def name= name
@name = name
# guess the gender
if @male_names.include? name
@gender = 'male'
elsif @female_names.include? name
@gender = 'female'
end
end
# gender setter
def gender= gender
if ['male', 'female'].include? gender
@gender = gender
else
raise "I thought a human can only be a male or female, #{gender} given"
end
end
end
# In gebruik
jan = Person.new
jan.name = 'jan'
puts jan.gender # 'male'
nienke = Person.new
nienke.name = 'nienke'
puts nienke.gender # 'female'
# maakt getter aan
attr_reader :name, :gender
def initialize
@male_names ['jan', 'ben', 'klaas', ...]
@female_names ['nienke', ...]
end
# setter voor naam
def name= name
@name = name
# guess the gender
if @male_names.include? name
@gender = 'male'
elsif @female_names.include? name
@gender = 'female'
end
end
# gender setter
def gender= gender
if ['male', 'female'].include? gender
@gender = gender
else
raise "I thought a human can only be a male or female, #{gender} given"
end
end
end
# In gebruik
jan = Person.new
jan.name = 'jan'
puts jan.gender # 'male'
nienke = Person.new
nienke.name = 'nienke'
puts nienke.gender # 'female'