[oop] in dubio: private vs protected
Ik heb een lastige. Ik weet dat sommigen van jullie de voorkeur er aan geven om alle class-properties private te maken. En ik snap dat ook. Want dan weet je zeker dat de properties niet van buitenaf kunnen worden aangepast en dat er geen ongeldige waardes kunnen worden geset.
Wat doe je echter als je een child- en een parent-class hebt? Geldt dan hetzelfde principe? Gebruik je nog steeds uitsluitend private properties? En heeft de child-class uitsluitend via methods van de parent-class toegang tot die properties? Of maak je de properties van de parent-class protected zodat de child-class er zelf bij kan? Van de ene kant neig ik ernaar om alles private te houden. Echter, dat betekent tegelijkertijd dat je extra (protected) methods moet maken in de parent-class die je uitsluitend gebruikt om de child- en parent-class met elkaar te laten "praten".
Zomaar een voorbeeld met een protected property:
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
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
<?php
class A {
protected $foo
public function __construct(array $foo) {
$this->foo = $foo;
}
public function get($id) {
return $this->foo[$id];
}
}
class B extends A {
private $bar;
public function __construct(array $foo, $bar) {
$this->foo = $foo;
$this->bar = $bar;
}
public function bar() {
if(isset($this->foo['bar'])) {
$this->bar = $this->foo;
}
}
}
?>
class A {
protected $foo
public function __construct(array $foo) {
$this->foo = $foo;
}
public function get($id) {
return $this->foo[$id];
}
}
class B extends A {
private $bar;
public function __construct(array $foo, $bar) {
$this->foo = $foo;
$this->bar = $bar;
}
public function bar() {
if(isset($this->foo['bar'])) {
$this->bar = $this->foo;
}
}
}
?>
Nu dezelfde classes, maar deze keer is $foo private (let op de 2 extra methods in de parent-class).
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
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
<?php
class A {
private $foo
public function __construct(array $foo) {
$this->foo = $foo;
}
public function get($id) {
return $this->foo[$id];
}
protected function getAll() {
return $this->foo;
}
protected function has($id) {
return isset($this->foo[$id]);
}
}
class B extends A {
private $bar;
public function __construct(array $foo, $bar) {
parent::__construct($foo);
$this->bar = $bar;
}
public function bar() {
if($this->has('bar')) {
$this->bar = $this->getAll();
}
}
}
?>
class A {
private $foo
public function __construct(array $foo) {
$this->foo = $foo;
}
public function get($id) {
return $this->foo[$id];
}
protected function getAll() {
return $this->foo;
}
protected function has($id) {
return isset($this->foo[$id]);
}
}
class B extends A {
private $bar;
public function __construct(array $foo, $bar) {
parent::__construct($foo);
$this->bar = $bar;
}
public function bar() {
if($this->has('bar')) {
$this->bar = $this->getAll();
}
}
}
?>
Zoals je ziet is het laatste voorbeeld qua code wat langer. Daarentegen is deze optie wel wat veiliger.
Tja... wat is nou wijsheid?
Private properties zijn veiliger. Dat is de ene kant van het verhaal. De andere kant is dat je een extra "communicatielaag" moet aanbrengen in de parent class enkel en alleen om de child-class met de parent-class te kunnen laten praten. Je zou ook kunnen zeggen dat een child- en parent-class eigenlijk 1 class is, en dat de child-class dus rechtstreeks toegang moet kunnen hebben tot de properties van z'n eigen class (ook al staan die properties in de parent class).
Wie kan mij een zeer steekhoudende reden geven om dan wel voor de werkwijze met private properties te kiezen, dan wel voor de werkwijze met protected properties?
Ik ben benieuwd naar jullie meningen.
Gewijzigd op 15/03/2014 02:35:38 door Ozzie PHP
Dat is nu precies een belangrijk verschil. Op het moment dat je een parent ontwerpt voor inheritance, kies je waar nodig voor protected. Daarmee publiceer je meteen een API die aan anderen aangeeft: je mag de eigenschap gebruiken. Dat geeft meer flexibiliteit, maar de prijs die je daarvoor moet betalen, is een grotere verantwoordelijkheid. Je weet bij een protected eigenschap dat de code van subklassen kan breken op het moment dat je de parent herschrijft.
In overige gevallen gebruik je private om de eigenschap te beschermen en niet te publiceren. Wil je de klasse helemaal dichttimmeren, dan maak je deze ook nog final.
>> Op het moment dat je een parent ontwerpt voor inheritance, kies je waar nodig voor protected.
Ik snap wat je bedoelt. Vaak weet ik zoiets van tevoren nog niet. Stel je hebt een product class, en ineens heb je daar een "afgeleide" van nodig. Dan kun je dus de properties van de parent class in plaats van private protected maken. Maar dit weet je niet altijd op voorhand.
Ander voorbeeld... Ik heb een abstracte autoloader class waarmee je namespaces kunt setten. De namespaces (en hun bijbehorende path) staan op dit moment als een protected property (array) in de abstracte class. De child classes kunnen dus makkelijk gebruik maken van die namespaces.
Het voordeel is nu dat een child-class makkelijk kan kijken of een bepaalde namespace bestaat. Zou de property private zijn, dan moet ik telkens via een method in de abstracte class gaan kijken of de namespace bestaat. Je krijgt dus een hoop extra "verkeer" heen-en-weer. Dat zou dus weer een reden kunnen zijn om toch voor protected te kiezen.
Het enige nadeel aan protected vind ik dat in bovengenoemd voorbeeld een programmeur vanuit de child class de namespaces zou kunnen overschrijven/wijzigen. Of moet je er gewoon vanuit gaan dat een programmeur zo slim is dat ie dat niet doet?
Nog heel anders gesteld dan... moet je er vanuit gaan dat child- en parent-class vriendjes van elkaar zijn, en dus altijd zullen doen wat goed is? Of moet je er vanuit gaan dat het "vijanden" van elkaar zijn, en moet je er dus voor zorgen dat ze elkaar niks kunnen aandoen?
>> Het enige nadeel aan protected vind ik dat in bovengenoemd voorbeeld een programmeur vanuit de child class de namespaces zou kunnen overschrijven/wijzigen. Of moet je er gewoon vanuit gaan dat een programmeur zo slim is dat ie dat niet doet?
Die programmeur moet doen wat die programmeur goed vindt. Je kunt niet in zijn/haar use case kijken. Misschien is het zelfs wel iets heel slims waaraan je zelf nog nooit had gedacht.
>> Nog heel anders gesteld dan... moet je er vanuit gaan dat child- en parent-class vriendjes van elkaar zijn, en dus altijd zullen doen wat goed is? Of moet je er vanuit gaan dat het "vijanden" van elkaar zijn, en moet je er dus voor zorgen dat ze elkaar niks kunnen aandoen?
Geen vriend/vijand, maar eerder ouder/kind-verantwoordelijkheid. Door een eigenschap protected te maken, geef je een kind de vrijheid om iets zelf te regelen. Maar het kind krijgt daarmee ook een eigen verantwoordelijkheid. Bovendien krijgt de ouder bij protected de extra verantwoordelijkheid om toe te zien op de verworven vrijheden: ze moeten blijven werken zoals ze werkten en ze moeten beschikbaar blijven, anders breekt de code van een kind.
>> ze moeten blijven werken zoals ze werkten en ze moeten beschikbaar blijven, anders breekt de code van een kind.
Dat is inderdaad een goede. Nog niet eens aan gedacht.
Hoe ga jij hier zelf eigenlijk mee om? Ik meen bijvoorbeeld dat Wouter altijd alle properties private maakt. Doe jij dat ook> Of gebruik je zelf ook wel eens protected properties?
Eigenlijk zoals je het zelf al beargumenteerde, maar dan op de automatische piloot.
Toevoeging op 15/03/2014 18:08:05:
Om er nog een leuke vraag bovenop te gooien:
Wat nu als je alle properties in plaats van private protected zou maken? En dus nooit meer private properties zou gebruiken? Anders gezegd, een child- en parent-class kunnen altijd bij elkaars properties, maar van buitenaf kun je er nooit bij. Is dat nog een idee?
Juist wel. Ik maak alles private, tenzij er een communicatielaag is in bijvoorbeeld de vorm van een abstract class.
>> Wat nu als je alle properties in plaats van private protected zou maken?
Dan geef je zowel parent als children dus véél meer verantwoordelijkheden en haal je hoogstwaarschijnlijk ook véél meer werk op de hals.
Doe je dat enkel bij een abstract class? Stel je hebt een niet-abstracte Product class, en ineens wil je een Superproduct class maken. Dit wordt dan een child van de Product class. Stel die Superproduct class heeft nu een property nodig van de Product class. Maak je dan die betreffende property in de product class protected? Of ga je in de Product class een extra protected method maken om die property door te geven aan de Superproduct class?
Ik heb er goed over nagedacht en ik denk dat ik nu een goede "regel" heb gevonden. Als een child een property nodig heeft van z'n parent, dan maak je die property protected (zelfs als deze property via een public getter in de parent beschikbaar is, want aanroepen via een extra method gaat ten koste van de performance). Zodra een property protected is, weet je dan ook gelijk dat deze door een child class wordt gebruikt.
Je begint met private en schakelt pas op protected over als een child dat nodig heeft. Op dat moment ontstaan er twee nieuwe verantwoordelijkheden: child mag/moet een property implementeren en parent heeft de extra zorg over een property die niet langer private (en dus privé) maar protected (en dus gedeeld is).
Abstract classes verschillen daarin maar in één opzicht: je weet op voorhand dat ze een child hebben, want anders zijn ze sowieso onbruikbaar.
Meer technisch mag je ook zeggen dat een property onderdeel wordt van de openbare API zodra je een eigenschap protected in plaats van private maken. En dat betekent meer verantwoordelijkheden, beter documenteren en niet meer te wijzigen met een minor update, alleen nog een major update die de API verandert en potentieel code breekt.
Wat betreft abstracte classes. Ik zou niet gelijk ALLE properties protected maken. Het kan namelijk zo zijn dat de abstracte class een stukje basisfunctionaliteit heeft waar de child class niks mee te maken heeft, maar waar wel een property voor nodig is. Pas als er een child class is die toch die property nodig blijkt te hebben, pas dan zou ik 'm protected maken. Dus vuistregel: alles private, en pas protected op het moment dat een child class de property daadwerkelijk nodig heeft.