Probleem output class
Ik ben net begonnen met (lezen van) een boek over OOP en kom er nu al niet uit. Hopelijk kan iemand mij uitleggen wat ik verkeerd doe. Ik zeg er bij dat er geen opdrachten in het boek zitten, maar dat ik wel alle scripts uitschrijf en dan evt uitbreidt aan de hand van de stof om er zoveel mogelijk van op te pikken.
Het voorbeeld hieronder is trouwens een voorbeeld van hoe het niet moet maar dat staat los van mijn probleem. (volgende alinea gaat over inheritance en moet ik leren hoe het wel moet, haha)
Het boek laat een class zien met de naam ShopProduct. In deze class kun je zowel boeken als cd's aanmaken. Via de omslachtige functie getType moet je testen of je met een boek of cd te maken hebt. Deze functie wordt in het boek niet voorgedaan en heb ik dus zelf geschreven. De rest van de code komt letterlijk uit het boek, behalve dan dat aan getSummaryLine een extra regel (ik kan helemaal geen type vinden) heb toegevoegd om wat meer inzicht te krijgen in wat er nou mis gaat.)
De output van dit script is dus "ik kan helemaal geen type vinden". Dus ik vermoed dat er iets mis gaat in de functie getType. Echter, helemaal onderaan het script print ik bij wijze van test functie getType en dan krijg ik wel als output "book". Ik snap dus niet waarom functie getSummaryLine niet vaststelt dat het om een boek gaat.
Alvast mijn excuses als ik iets heel doms gedaan heb maar werk voor het eerst met classes dus moet nog heel veel leren...
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
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
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
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
<?php
class ShopProduct {
public $numPages;
public $playLength;
public $title;
public $producerMainName;
public $producerFirstName;
public $price;
public $type;
function __construct ($title, $firstName, $mainName, $price, $numPages=0, $playLength=0 ) {
$this->title = $title;
$this->producerFirstName = $firstName;
$this->producerMainName = $mainName;
$this->price = $price;
$this->numPages = $numPages;
$this->playLength = $playLength;
}
function getNumberOfPages() {
return $this->numPages;
}
function getPlayLength() {
return $this->playLength;
}
function getProducer() {
return "{$this->producerFirstName}"."{$this->producerMainName}";
}
function getType() {
if ($this->numPages != 0){
$this->type = "book";
} else {
$this->type = "cd";
}
return $this->type;
}
function getSummaryLine() {
$base = "{$this->title} ( {$this->producerMainName}, ";
$base .= "{$this->producerFirstName} )";
if ($this->type == "book" ) {
$base .= ": page count - {$this->numPages}";
} else if ( $this->type == "cd" ) {
$base .= ": playing time - {$this->playLength}";
} else {
$base .= "ik kan helemaal geen type vinden";
}
return $base;
}
}
$product1 = new shopProduct("Boek over PHP", "Matt", "Zandstra", 5.99, 235, 0);
print $product1->getSummaryLine();
print $product1->getType();
?>
class ShopProduct {
public $numPages;
public $playLength;
public $title;
public $producerMainName;
public $producerFirstName;
public $price;
public $type;
function __construct ($title, $firstName, $mainName, $price, $numPages=0, $playLength=0 ) {
$this->title = $title;
$this->producerFirstName = $firstName;
$this->producerMainName = $mainName;
$this->price = $price;
$this->numPages = $numPages;
$this->playLength = $playLength;
}
function getNumberOfPages() {
return $this->numPages;
}
function getPlayLength() {
return $this->playLength;
}
function getProducer() {
return "{$this->producerFirstName}"."{$this->producerMainName}";
}
function getType() {
if ($this->numPages != 0){
$this->type = "book";
} else {
$this->type = "cd";
}
return $this->type;
}
function getSummaryLine() {
$base = "{$this->title} ( {$this->producerMainName}, ";
$base .= "{$this->producerFirstName} )";
if ($this->type == "book" ) {
$base .= ": page count - {$this->numPages}";
} else if ( $this->type == "cd" ) {
$base .= ": playing time - {$this->playLength}";
} else {
$base .= "ik kan helemaal geen type vinden";
}
return $base;
}
}
$product1 = new shopProduct("Boek over PHP", "Matt", "Zandstra", 5.99, 235, 0);
print $product1->getSummaryLine();
print $product1->getType();
?>
Toevoeging op 24/10/2016 14:06:35:
Als aanvulling, als ik $type gewoon in de contruct functie toevoeg werkt het wel maar het idee is juist dat het aan de hand van $numPages bepaald moet worden...
Je zult eerst getType een keer moeten aanroepen om het type geset te krijgen, of getType gebruiken in je class ipv $this->type.
Als iets uitsluitend een boek óf een cd is, kun je dat in deze klasse kennelijk met $numPages=0, $playLength=0 in de constructor regelen: een boek heeft immers pagina's maar geen speellengte en een cd heeft omgekeerd een speellengte maar geen pagina's. Geen fraaie oplossing (want je beter de klasse extenden voor een boek of een cd), maar deze hint in de constructor wijst wel die kant op.
Je misbruikt nu de getType() method. Een (misschien ongeschreven) wet is dat je met een get...() functie (ook wel getter genoemd) informatie leest en met een set...() functie (of setter) iets schrijft.
Jouw code zou dus veel logischer zijn met deze twee methods:
Code (php)
Gewijzigd op 24/10/2016 14:44:42 door Frank Nietbelangrijk
De gedachte hierachter is simpel: als je op meerdere manieren het type kunt achterhalen, dan komen er gegarandeerd verschillen in en dan zegt de ene methode dat het en boek is terwijl de ander zegt dat het een cs is.
ps: zoals je het nu hebt is een product een cd zolang het geen pagina's heeft, heeft het wel pagina's dan is het ineens een boek. Los van de opmerking over overerven (waar ook weer een hele discussie bij komt kijken over hoe praktisch dat is) kun je denk ik beter een setType() maken waarin jij gewoon kunt aangeven wat voor product het is. Je stelt letterlijk de waarde van $this->type in op 'cd' of 'boek', dan verandert het type niet elke keer dat de inhoud van het product om welke reden dan ook stomtoevallig even voldoet aan de definitie van een cd of boek.
Daarnaast kun je in de methodes die b.v. het aantal pagina's instellen meteen naar het type kijken en een foutmelding geven als iemand probeert om het aantal pagina's in te stellen terwijl het om een cd gaat.
@Ben van Velzen, dat wist ik nog niet, en is wel nuttig om te weten!! Ik heb net in getSummaryLine GetType() eerst aangeroepen en dan werkt het script naar behoren. zoals PG Vincent ook zegt.
Rare vraag misschien (zoals ik al zei, ik ben nieuw met classes en heb ook alleen maar ervaring met hele basic functies) maar is het "netjes" om in een functie eerst een andere functie aan te roepen?
@Pg Vincent, wat je in je ps. zegt is helemaal waar, ik vermoed dat de rest van mijn hoofdstuk hier verder op in gaat. Maar één stapje tegelijk ;)
@Ward van der Put, dat klopt idd. Het boek legt zover steeds eerst allemaal "foutieve" methoden uit om dan te eindigen met de juiste, maar ik kreeg de foute methode niet aan het werk dus daar wilde ik me eerst toch even doorheen slaan. Leer ik weer van.
Quote:
Rare vraag misschien (zoals ik al zei, ik ben nieuw met classes en heb ook alleen maar ervaring met hele basic functies) maar is het "netjes" om in een functie eerst een andere functie aan te roepen?
Het is gebruikelijk, maar het is afhankelijk van wat je aan het doen bent. Hier heb je een getter functie gemaakt die side effects heeft. Dat is op zichzelf al uit den boze.
Ok, dank je. Het hele getter setter verhaal is me nog niet helemaal duidelijk maar zie dat ik daar nog ga komen over 20 bladzijden dus dat laat ik tot dan maar even voor wat het is.
In het kort ...
Met een setter stel je iets in (je 'set' een waarde).
Met een getter haal je een waarde op (je 'get' een waarde).
Setters: setAmount, setName, setTitle
Getters: getAmount, getName, getTitle
In de praktijk:
Code (php)
1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
<?php
$member_name = 'Jan'; // dit haal je bijv. uit de database
$member = new Member();
$member->setName($member_name);
// ergens anders in je script
echo 'Welkom ' . $member->getName();
?>
$member_name = 'Jan'; // dit haal je bijv. uit de database
$member = new Member();
$member->setName($member_name);
// ergens anders in je script
echo 'Welkom ' . $member->getName();
?>
>> maar is het "netjes" om in een functie eerst een andere functie aan te roepen?
Hangt er een beetje vanaf. Normaal staat een functie op zichzelf en voert deze één taak uit. Soms kan het zo zijn dat verschillende functies "een zelfde stukje taak" moeten uitvoeren. Voor "datzelfde stukje taak" kun je dan een aparte functie schrijven, die je vanuit die andere functies aanroept.
In fictieve code:
- maakwoonkamerschoon()
- maakzolderschoon()
- maakslaapkamersschoon()
- maakkeuken schoon()
- maakkelderschoon()
Met deze functies kun je delen van het huis schoonmaken.
Als je echter het hele huis wil schoon maken krijg je:
Code (php)
1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
<?php
maakhuisschoon() {
maakwoonkamerschoon();
maakzolderschoon();
maakslaapkamersschoon();
maakkeuken schoon();
maakkelderschoon();
}
?>
maakhuisschoon() {
maakwoonkamerschoon();
maakzolderschoon();
maakslaapkamersschoon();
maakkeuken schoon();
maakkelderschoon();
}
?>
Een stom voorbeeldje, maar het gaat even om de gedachtegang.
Quote:
Met een getter haal je een waarde op (je 'get' een waarde).
En wellicht handig om te weten / belangrijk om te benadrukken:
getters en setters zijn een conventie, een goede gewoonte. Je zou immers ook rechtstreeks de property kunnen aanspreken.
De reden voor getters en setters is dat je daarin code kunt plaatsen die de waarde bewerken en/of controleren en in kunnen grijpen als het mis gaat.
Stel dat je een email gaat versturen, dan wil je dat het ingestelde email adres ook echt een adres is, je wilt niet pas bij het versturen merken dat je "asdasd" als adres hebt gekregen, dus je setEmail() method controleert of de waarde die wordt "geset" wel een email adres is. Zoniet, dan laat je de setEmail() een fout produceren en dan weet het script dat probeert om het email adres in te stellen meteen dat het niet gaat werken. Zonder geset email adres kun je geen mail versturen, en zo blijft je een hoop ellende bespaard.
Op dezelfde manier zou een setPrice() nooit een negatief getal toestaan, en zou je met iets als getPrice("centen") de prijs terugkrijgen in centen in plaats van euro's; "145" in plaats van "1.45"
Wat is een 'nette' manier om de waarde van $this->numPages in $this->type te krijgen?
Als ik de code van Frank Nietbelangerijk van hierboven neem:
Code (php)
maar hoe verwerk ik dit dan zo dat er uiteindelijk uitkomt of het om een boek of een cd gaat op basis van $this->numPages? Nog een nieuwe functie maken waarin ik $this->numPages ophaal en bekijk en vaststel of het een boek of een cd is? Want dan heb ik uiteindelijk 3 functies die hetzelfde doen als nu die ene functie...
En wat betreft functies, even uitgaande van de code waarmee ik het topic opende ook al is die niet zo netjes, is het dan het beste om getType aan te roepen in getSummaryLine, of om beide achter elkaar te gebruiken in de uiteindelijke uitvoerende code, of om nog een derde functie te maken die niets anders doet dan die twee functies te combineren? Of nog iets heel anders?
Nogmaals sorry als het domme vragen zijn maar ik wil het graag begrijpe :)
Wanneer je toch bepaalt op basis van $this->numPages heb je in principe dan $this->type niet nodig. Het voelt hooguit wat vreemd om op basis van het aantal pagina's het type te bepalen. Hoe classificeer je een dubbelcd bijvoorbeeld?
Je kunt uiteraard ook in de functie setNumPages bepalen wat het type wordt, en dan getType() binnen de class niet gebruiken. Het is maar net wat in de huidige situatie handiger is. Er is geen 1 formule die alles dekt.
Marlies Maalderink op 25/10/2016 10:50:53:
Wat is een 'nette' manier om de waarde van $this->numPages in $this->type te krijgen?
Je had dit al in de getter staan:
Code (php)
Hier staat de logica van je klasse: als iets pagina's heeft, is het een boek. Je zou die logica kunnen verplaatsen naar de constructor, aangezien je dáár (en alleen daar) het aantal pagina's instelt:
Code (php)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?php
public function __construct($title, $firstName, $mainName, $price, $numPages = 0, $playLength = 0)
{
$this->title = $title;
$this->producerFirstName = $firstName;
$this->producerMainName = $mainName;
$this->price = $price;
$this->numPages = $numPages;
$this->playLength = $playLength;
if ($this->numPages != 0) {
$this->type = 'book';
} else {
$this->type = 'cd';
}
}
public function getType()
{
return $this->type;
}
?>
public function __construct($title, $firstName, $mainName, $price, $numPages = 0, $playLength = 0)
{
$this->title = $title;
$this->producerFirstName = $firstName;
$this->producerMainName = $mainName;
$this->price = $price;
$this->numPages = $numPages;
$this->playLength = $playLength;
if ($this->numPages != 0) {
$this->type = 'book';
} else {
$this->type = 'cd';
}
}
public function getType()
{
return $this->type;
}
?>
Met een aparte setter wordt hetzelfde:
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
public function __construct($title, $firstName, $mainName, $price, $numPages = 0, $playLength = 0)
{
$this->title = $title;
$this->producerFirstName = $firstName;
$this->producerMainName = $mainName;
$this->price = $price;
$this->numPages = $numPages;
$this->playLength = $playLength;
if ($this->numPages != 0) {
$this->setType('book');
} else {
$this->setType('cd');
}
}
private function setType($type)
{
$this->type = $type;
}
public function getType()
{
return $this->type;
}
?>
public function __construct($title, $firstName, $mainName, $price, $numPages = 0, $playLength = 0)
{
$this->title = $title;
$this->producerFirstName = $firstName;
$this->producerMainName = $mainName;
$this->price = $price;
$this->numPages = $numPages;
$this->playLength = $playLength;
if ($this->numPages != 0) {
$this->setType('book');
} else {
$this->setType('cd');
}
}
private function setType($type)
{
$this->type = $type;
}
public function getType()
{
return $this->type;
}
?>
Nogmaals, dat er allemaal haken en ogen aan kleven om op deze manier te bepalen of het om een boek of cd gaat (en wat dan als er ineens ook nog een dubbel cd zoals Ben van Velzen zegt bij zou komen ofzo) dat weet ik, ga nu verder met de paragraaf over hoe het dan wel moet. Maar door dit stukje besefte ik dat ik eea niet helemaal begreep en nu begrijp ik het wel. Dus ik kan weer verder, bedankt! :)
Gewijzigd op 25/10/2016 11:26:32 door Marlies Maalderink
Te meer om het volgende: objecten zijn (zouden?) meestal gebruiksklaar (moeten zijn?) na constructie. Dit houdt tevens in dat alle klasse-variabelen geïnitialiseerd zouden moeten zijn (een beginwaarde ontvangen zouden moeten hebben). Jouw probleem ontstond juist doordat $this->type nog geen waarde had op het moment dat je deze wilde gaan gebruiken in de methode getSummaryLine().
Het is altijd verstandig om elke (klasse-)variabele te voorzien van een (expliciete,) initiële waarde.
>> Het boek laat een class zien met de naam ShopProduct. In deze class kun je zowel boeken als cd's aanmaken.
Dat lijkt me al vreemd, dat je een algemene class gebruikt om een specifiek product aan te maken. Ward gaf dit eerder ook al aan zie ik.
Door dit soort rare voorbeelden zou je dus met één en dezelfde class een bankstel en een vliegtuig kunnen aanmaken (want een vliegtuig heeft 2 vleugels ... hmmm, juist ja). Als je het mij vraagt, leer je door dit soort voorbeelden niet goed programmeren.
In de realiteit zul je dus eerder gaan werken als volgt:
Het is heel raar om een object aan te maken, en vervolgens aan dat object te moeten vragen 'zeg ehhh ... wat ben jij eigenlijk voor een object?'.
Gewijzigd op 25/10/2016 16:42:22 door Ozzie PHP
Thomas van den Heuvel op 25/10/2016 15:19:04:
Te meer om het volgende: objecten zijn (zouden?) meestal gebruiksklaar (moeten zijn?) na constructie. Dit houdt tevens in dat alle klasse-variabelen geïnitialiseerd zouden moeten zijn (een beginwaarde ontvangen zouden moeten hebben). Jouw probleem ontstond juist doordat $this->type nog geen waarde had op het moment dat je deze wilde gaan gebruiken in de methode getSummaryLine().
Het is altijd verstandig om elke (klasse-)variabele te voorzien van een (expliciete,) initiële waarde.
Het is altijd verstandig om elke (klasse-)variabele te voorzien van een (expliciete,) initiële waarde.
Duidelijk verhaal! dank je :)
Toevoeging op 25/10/2016 16:59:00:
Ozzie PHP op 25/10/2016 16:40:34:
Even terug naar het begin ...
>> Het boek laat een class zien met de naam ShopProduct. In deze class kun je zowel boeken als cd's aanmaken.
Dat lijkt me al vreemd, dat je een algemene class gebruikt om een specifiek product aan te maken. Ward gaf dit eerder ook al aan zie ik.
Door dit soort rare voorbeelden zou je dus met één en dezelfde class een bankstel en een vliegtuig kunnen aanmaken (want een vliegtuig heeft 2 vleugels ... hmmm, juist ja). Als je het mij vraagt, leer je door dit soort voorbeelden niet goed programmeren.
In de realiteit zul je dus eerder gaan werken als volgt:
Het is heel raar om een object aan te maken, en vervolgens aan dat object te moeten vragen 'zeg ehhh ... wat ben jij eigenlijk voor een object?'.
>> Het boek laat een class zien met de naam ShopProduct. In deze class kun je zowel boeken als cd's aanmaken.
Dat lijkt me al vreemd, dat je een algemene class gebruikt om een specifiek product aan te maken. Ward gaf dit eerder ook al aan zie ik.
Door dit soort rare voorbeelden zou je dus met één en dezelfde class een bankstel en een vliegtuig kunnen aanmaken (want een vliegtuig heeft 2 vleugels ... hmmm, juist ja). Als je het mij vraagt, leer je door dit soort voorbeelden niet goed programmeren.
In de realiteit zul je dus eerder gaan werken als volgt:
Het is heel raar om een object aan te maken, en vervolgens aan dat object te moeten vragen 'zeg ehhh ... wat ben jij eigenlijk voor een object?'.
Ozzie, je hebt helemaal gelijk.Zoals ik als aangaf, dit script is een voorbeeld van waarom het niet handig is om een class te maken die meerdere verschillende objecten denkt die allemaal verschillende eigenschappen hebben. (ben bezig met 'PHP Objects, Patterns, and Practice van Matt Zandstra') Volgende alinea houdt zich er mee bezig hoe dat beter kan. Maar omdat ik hier even vastliep omdat ik de $type er niet in kreeg wilde ik dat eerst even duidelijk hebben voor mezelf, voor ik in het volgende stuk duik. En hier nog vragen over had.
Het is jammer dat er geen opdrachten met uitleg bij het boek zitten, er wordt een stuk code gegeven en daar dan bij gezegd dat je de $type property kan instellen door $numPages te testen, maar nergens staat hoe. Dat probeerde ik dus voor elkaar te krijgen.
Ik gaf het alleen even aan omdat er genoeg boeken zijn die met dit soort foute voorbeelden strooien en het zou jammer zijn als jij vervolgens denkt 'dat het zo hoort' en het dus ook zo gaat doen.
>> ... kan instellen door $numPages te testen, maar nergens staat hoe.
Dat is iets waar je aan zult moeten wennen bij computerboeken. Vaak zijn die geschreven door programmeurs die niet altijd even goed in staat zijn om vanuit het perspectief van een beginner naar hun eigen code te kijken, laat staan met een kritische blik naar hun eigen code te kijken. Ik ben nog nooit een computerboek tegengekomen waar geen fouten in staan. Het probleem is dat je die fouten als beginner niet herkent en je dus geen idee hebt wat je fout doet. Als je wat meer gevorderd bent, herken je zulke fouten wel en kun je je alleen maar afvragen waarom de auteur iets heeft opgeschreven en klaarblijkelijk niet heeft getest.
Anyhow ... lang verhaal kort: verbaas je niet over fouten in computerboeken. En als je ergens niet uitkomt dan horen we het wel weer ;-)
Goed om te weten Ozzie! Als ik weer vastloop dan laat ik het weten, fijn dat jullie allemaal hebben geholpen en meegedacht :)