[PHP] Constructor overschrijven of init functie maken?

Pagina: 1
Acties:
  • 183 views sinds 30-01-2008
  • Reageer

Onderwerpen


Acties:
  • 0 Henk 'm!

  • Burat
  • Registratie: Oktober 1999
  • Niet online

Burat

bos wortels

Topicstarter
Dames en heren OO-proggers, ik heb een vraag. We hebben een discussie hier over extenden van classes in PHP 5 - of een willekeurige andere OO taal. We hebben een grote klasse met een constructor met 8 parameters, die we heel vaak gaan extenden. De extensie hoeft die parameters vrijwel nooit aan te passen - maar moet wel vaak iets doen tijdens de instantiatie.

Nu zijn er twee mogelijkheden:
1. In de child-class de constructor overschrijven, daar de parent-constructor aanroepen en je eigen ding doen.
2. In de parent-class een lege init-method maken, die aanroepen in de constructor en in de child-classes alleen de init-method overschrijven.

De twee mogelijkheden in PHP snippet onder elkaar:
PHP:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class RelationItemList extends AdminItemList
{
    public function __construct($query, $resPerPage = ItemList::DEFAULT_RES_PER_PAGE, $position = 1, $isSearchable = true, $isSortable = true, $hasPages = true, $hasPrevNext = true, $hasFirstLast = true)
    {
        parent::__construct($query, $resPerPage, $isSearchable, $isSortable, $hasPages, $hasPrevNext, $hasFirstLast);

        $this->foo = 'bar';
    }
}

class RelationItemList extends AdminItemList
{
    public function init()
    {
        $this->foo = 'bar';
    }
}


Wat is mooier, beter, handiger volgens jullie?

Homepage | Me @ T.net | Having fun @ Procurios | Collega's gezocht: Webontwikkelaar PHP


Acties:
  • 0 Henk 'm!

  • whoami
  • Registratie: December 2000
  • Laatst online: 14:45
Als je die waarden altijd nodig hebt om een goeie werking van je class te garanderen, zou ik ze in de constructor opnemen.

https://fgheysels.github.io/


Acties:
  • 0 Henk 'm!

  • Burat
  • Registratie: Oktober 1999
  • Niet online

Burat

bos wortels

Topicstarter
whoami schreef op dinsdag 07 december 2004 @ 09:31:
Als je die waarden altijd nodig hebt om een goeie werking van je class te garanderen, zou ik ze in de constructor opnemen.
Hoe bedoel je? Welke van de twee is dan de beste manier?
[edit]: in beide gevallen wordt de volledige constructor met alle parameters aangeroepen. In het tweede voorbeeld erft het child-object de constructor over van de parent.

[ Voor 23% gewijzigd door Burat op 07-12-2004 09:34 ]

Homepage | Me @ T.net | Having fun @ Procurios | Collega's gezocht: Webontwikkelaar PHP


Acties:
  • 0 Henk 'm!

Verwijderd

Burat schreef op dinsdag 07 december 2004 @ 09:33:
[...]


Hoe bedoel je? Welke van de twee is dan de beste manier?
[edit]: in beide gevallen wordt de volledige constructor met alle parameters aangeroepen. In het tweede voorbeeld erft het child-object de constructor over van de parent.
Denk dat whoami bedoelt dat, als je bepaalde parameters altijd hetzelfde aan gaat roepen in deze extensie, je die dan beter direct vanuit je nieuwe constructor kunt meegeven zoals dit:
PHP:
1
2
3
4
function __construct()
{
    parent::__construct('foo', 'bar');
}


Dit is echter in dit geval niet van toepassing, de resulterende class moet op dezelfde wijze(n) te instantieren zijn als de parent.

Ben btw collega van Burat, vandaar dat ik weet wat de bedoeling is ;)

Acties:
  • 0 Henk 'm!

  • Haploid
  • Registratie: Maart 2002
  • Laatst online: 29-12-2021

Haploid

Doh!

Eigenlijk zijn de beide oplossingen gelijkwaardig. Je gooit bij oplossing 2 alleen een deel van de code uit de constructor in een eigen functie voor makkelijk hergebruik, net zoals je dat elders in het programma zou doen. Ik heb geen specifieke ervaring met PHP, maar ik weet dat je in C++ de constructor van de derived class zo kunt maken dat hij de constructor van de base class aanroept. Dat ziet er ongeveer zo uit:
C++:
1
2
3
4
5
6
class RelationItemList : public AdminItemList
{
    RelationItemList() : AdminItemList() { // hier wordt meteen de base class constructor aangeroepen
        // code specifiek voor RelationItemList
    }
}

Als PHP iets dergelijks ondersteund, dan heb je een combinatie van oplossing 1 en 2. Lijkt me de meest elegante weg.

Hey, I came here to be drugged, electrocuted and probed, not insulted.


Acties:
  • 0 Henk 'm!

  • MSalters
  • Registratie: Juni 2001
  • Laatst online: 13-09 00:05
De discussie is taal-specifiek. In C++ bestaat een object pas als de ctor start. In een parent ctor bestaat de child nog niet, en als je probeert de init functie aan te roepen dan krijg je de parent versie. Andere OO talen maken andere keuzes, en laten je child.init aanroepen voordat de child ctor is aangeroepen.
Ook verschillen OO-talen in het effect van inheritance op ctors; in sommige talen hebben derived classes dezelfde ctors als hun parents maar dat is niet overal het geval.

Man hopes. Genius creates. Ralph Waldo Emerson
Never worry about theory as long as the machinery does what it's supposed to do. R. A. Heinlein


Acties:
  • 0 Henk 'm!

Verwijderd

Haploid schreef op dinsdag 07 december 2004 @ 09:51:
Eigenlijk zijn de beide oplossingen gelijkwaardig. Je gooit bij oplossing 2 alleen een deel van de code uit de constructor in een eigen functie voor makkelijk hergebruik, net zoals je dat elders in het programma zou doen. Ik heb geen specifieke ervaring met PHP, maar ik weet dat je in C++ de constructor van de derived class zo kunt maken dat hij de constructor van de base class aanroept. Dat ziet er ongeveer zo uit:
C++:
1
2
3
4
5
6
class RelationItemList : public AdminItemList
{
    RelationItemList() : AdminItemList() { // hier wordt meteen de base class constructor aangeroepen
        // code specifiek voor RelationItemList
    }
}

Als PHP iets dergelijks ondersteund, dan heb je een combinatie van oplossing 1 en 2. Lijkt me de meest elegante weg.
Dat zou echt geweldig handig zijn, maar helaas :'(
MSalters schreef op dinsdag 07 december 2004 @ 09:53:
De discussie is taal-specifiek. In C++ bestaat een object pas als de ctor start. In een parent ctor bestaat de child nog niet, en als je probeert de init functie aan te roepen dan krijg je de parent versie. Andere OO talen maken andere keuzes, en laten je child.init aanroepen voordat de child ctor is aangeroepen.
Ook verschillen OO-talen in het effect van inheritance op ctors; in sommige talen hebben derived classes dezelfde ctors als hun parents maar dat is niet overal het geval.
De discussie is op zich niet taalspecifiek, beide situaties werken, beide situaties doen hetzelfde. Met dat uitgangspunt, onafhankelijk van de taal, kun je een uitspraak doen over wat beter of correcter is om bepaalde redenen. Ik denk zelf dat de tweede versie mooier is omdat je dan de parent constructor met rust laat, je hoeft je als implementator niet druk te maken om de functionaliteiten binnen die functie en je alleen richten op de uitbreiding ervan.

edit:
Leuk, zo'n typo in een benadrukt woord :P

Acties:
  • 0 Henk 'm!

  • Skaah
  • Registratie: Juni 2001
  • Laatst online: 16-09 18:38
Bij de tweede oplossing hergebruik je meer code, dat lijkt me dus mooier. Elke keer zo'n lange constructor maken, is niet erg elegant en onderhoudbaar.

Acties:
  • 0 Henk 'm!

  • pjvandesande
  • Registratie: Maart 2004
  • Laatst online: 18-09 18:27

pjvandesande

GC.Collect(head);

Met methode 1, dus gewoon alles in de ctor. Vind ik een stuk netter omdat je zo verplicht alles te setten. Je kan wel in de Init method een flag setten dat alles ge-init is, maar dan moet je dat weer in elke functie controleren en dat leverd vind ik minder elegante code op.

Acties:
  • 0 Henk 'm!

  • Glimi
  • Registratie: Augustus 2000
  • Niet online

Glimi

Designer Drugs

(overleden)
Een constructor is een methode welke moet garanderen dat het object in correcte staat is. Opzich zou ik dus alle initialisatie code daar lekker houden. Een init() vind ik meer iets voor initialisatie voor een speciaal geval; denk Servlets etc.

Trouwens dit gaat ook weer niet op voor Java. Als er geen default ctor is, zul je die moeten aanroepen in je ctor van je klasse Ik zal dus altijd een ctor moeten maken en kan dus net zo goed de init code daar maken. Verder is er ook geen kwestie van een ctor overschrijven. Een ctor hoort bij een klasse en is per klasse (ook parent-child classes) op zichzelf.

Acties:
  • 0 Henk 'm!

  • Haploid
  • Registratie: Maart 2002
  • Laatst online: 29-12-2021

Haploid

Doh!

Verwijderd schreef op dinsdag 07 december 2004 @ 10:06:
Dat zou echt geweldig handig zijn, maar helaas :'(
Ach, het valt me nu pas op dat je al de constructor van de base class aanroept in oplossing 1. Dat is dan toch prima. Geen enkele reden om een init() functie erbij te maken, want die heb je al (alleen heet hij hier parent::__construct). Het is vrijwel altijd gewoonte om de base class constructor aan te roepen, zodat als er iets in de base class verandert (nieuw member ofzo), je dat niet in alle derived classes hoeft aan te passen.

Hey, I came here to be drugged, electrocuted and probed, not insulted.


Acties:
  • 0 Henk 'm!

Verwijderd

Ik zou lekker voor een virtuele init methode gaan. Het hangt een beetje van de functie af, maar als alle parameters aan de constructor anders onveranderd doorgegeven worden aan de parent constructor lijkt het me onzin om ze allemaal te gaan overtypen/copypasten, waarmee je kans op fouten introduceert en onderhoudbaarheid onderuit haalt als je later iets in de argumentlijst wilt veranderen.

Het klínkt ook alsof het meer om initialisatie dan constructie gaat, dus lijkt een init methode me ook meer op zijn plek.

En mocht je later in een kind de argumentlijst alsnog aan willen passen dan kan dat natuurlijk altijd nog, dus je beperkt jezelf er niet mee.

Dus:
• Makkelijker schrijfbaar
• Makkelijker leesbaar, mwa, lijkt me te debatteren
• Makkelijker onderhoudbaar
• Geen verlies van flexibiliteit

Wat let je? :p

Acties:
  • 0 Henk 'm!

  • Glimi
  • Registratie: Augustus 2000
  • Niet online

Glimi

Designer Drugs

(overleden)
Verwijderd schreef op dinsdag 07 december 2004 @ 10:58:
Ik zou lekker voor een virtuele init methode gaan. Het hangt een beetje van de functie af, maar als alle parameters aan de constructor anders onveranderd doorgegeven worden aan de parent constructor lijkt het me onzin om ze allemaal te gaan overtypen/copypasten, waarmee je kans op fouten introduceert en onderhoudbaarheid onderuit haalt als je later iets in de argumentlijst wilt veranderen.[...]
Ik snap niet waarom je de parentconstructor niet zou moeten aanroepen. Je moet je parentobject toch instancieren en als dat niet hoeft, waarom heeft ie dan uberhaupt een constructor gedefinieerd?
Ik weet trouwens dat het in PHP4 niet hoefde, maar dan kreeg je inconsistente objecten.

[ Voor 6% gewijzigd door Glimi op 07-12-2004 11:04 ]


Acties:
  • 0 Henk 'm!

Verwijderd

Glimi schreef op dinsdag 07 december 2004 @ 11:03:
[...]

Ik snap niet waarom je de parentconstructor niet zou moeten aanroepen. Je moet je parentobject toch instancieren en als dat niet hoeft, waarom heeft ie dan uberhaupt een constructor gedefinieerd?
Ik weet trouwens dat het in PHP4 niet hoefde, maar dan kreeg je inconsistente objecten.
Niemand zegt dat de parent constructor niet aangeroepen wordt. Als een class ge-extend wordt dan wordt, zolang je de constructor niet overschrijft, gewoon de parent constructor gebruikt.

In situatie 2 wordt dus de parent constructor gebruikt om te constructen, maar de child-init functie(s) worden gebruikt om specifieke initialisaties te doen.

Acties:
  • 0 Henk 'm!

Verwijderd

Haploid schreef op dinsdag 07 december 2004 @ 10:48:
[...]

Ach, het valt me nu pas op dat je al de constructor van de base class aanroept in oplossing 1. Dat is dan toch prima. Geen enkele reden om een init() functie erbij te maken, want die heb je al (alleen heet hij hier parent::__construct). Het is vrijwel altijd gewoonte om de base class constructor aan te roepen, zodat als er iets in de base class verandert (nieuw member ofzo), je dat niet in alle derived classes hoeft aan te passen.
De parent::__construct() is juist niet de init functie, deze willen we laten doen wat ie altijd al deed. Er moet dus extra functionaliteit bij die dan eventueel wel of niet in een andere functie geplaatst gaat worden, de parent constructor wordt dus, zoals ik hierboven ook aan Glimi uitleg, sowieso aangeroepen.

Acties:
  • 0 Henk 'm!

  • Glimi
  • Registratie: Augustus 2000
  • Niet online

Glimi

Designer Drugs

(overleden)
Verwijderd schreef op dinsdag 07 december 2004 @ 11:09:
Niemand zegt dat de parent constructor niet aangeroepen wordt. Als een class ge-extend wordt dan wordt, zolang je de constructor niet overschrijft, gewoon de parent constructor gebruikt.
Err, dan neem ik aan dat de compiler een default constructor genereerd (immers hoe moet hij anders weten hoe hij het object aanmaakt?) met daarin een call naar de parent-constructor. Dat doet hij voor elke parent constructor? Ik blijf het raar vinden. In ieder geval een feature die ik nog in geen enkele andere taal gezien heb.
In situatie 2 wordt dus de parent constructor gebruikt om te constructen, maar de child-init functie(s) worden gebruikt om specifieke initialisaties te doen.
Ja dat is duidelijk, maar als je puur members gaan initialiseren en toch normaal een constructor moet maken (Java, C++ etc) dan is het nut van een init nogal weg als hij niet echt moet initialiseren als het object al gecreerd is. Een nadeel van je init is trouwens dat hij hem ook weer in een beginstaat kan brengen, maar je parent-variabelen blijven nog gelijk.

Verder als je je base A hebt, B extends A en C extends B.
Je verwacht dat A bij z'n het aanroepen van z'n init wel een virtual call naar B z'n init() doet, maar wil je bij het constructen van C ook wel dat B de init() van C aanroept en dus de init() van B overslaat? Om dit te voorkomen, roept C++ dus in de constructor de init() van de class aan. Nouja, er zullen ook nog andere redenen zijn. Eigenlijk weet ik niet zeker of dit een reden is van de designers van C++


Ik sta trouwens nu wel een beetje met verbazing te kijken. Java pakt dus wel de virtual call naar init en roept dus ook de init van de deriving class aan. Dan kunnen dus de volgende problemen voorkomen:

Java:
1
2
3
4
5
6
7
8
9
10
11
12
public class Base {

    public Base() {

        init();
    }
    
    public void init( ) {

        System.out.println( "Base:init()" );
    }
}

Java:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Sub extends Base {

    private String _sub;

    public void init() {

        _sub    = "Sub:init()";
        System.out.println( "Sub:init()" );
    }

    public String getSub() {

        return _sub;
    }
}

Java:
1
2
3
4
5
6
7
8
9
10
11
12
13
public class SubSub extends Sub {

    public void init(){

        System.out.println( "SubSub:init()" );
    }

    public static void main( String[] args ) {

        SubSub theBase    = new SubSub();
        System.out.println( theBase.getSub() );
    }
}


Een uitvoer van de main levert op:
code:
1
2
SubSub:init()
null

Zo als je kunt zien, is door het overschrijven van init() in SubSub de init() in Sub niet aanroepen, wat tot gevolg heeft dat _sub niet ge-instancieerd is.

[ Voor 32% gewijzigd door Glimi op 07-12-2004 11:53 ]


Acties:
  • 0 Henk 'm!

Verwijderd

Glimi schreef op dinsdag 07 december 2004 @ 11:27:
...een aantal quotes en code...

Zo als je kunt zien, is door het overschrijven van init() in SubSub de init() in Sub niet aanroepen, wat tot gevolg heeft dat _sub niet ge-instancieerd is.
Ik hoop niet dat dit verrassend was? Je overschijft de init functie in al je subclasses, dus de laatste functie die init overschrijft wordt aangeroepen. Als je in je init functies van sub en subsub parent.init() zou aanroepen dan zou de output (volgens mij) moeten zijn:

Base:init();
Sub:init();
SubSub:init();
Sub:init();

Maar, we dwalen af, heeft iemand nog een overtuigend argument om één van beide opties juist wel, of juist niet, te doen?

[ Voor 74% gewijzigd door Verwijderd op 07-12-2004 12:20 ]


Acties:
  • 0 Henk 'm!

  • Glimi
  • Registratie: Augustus 2000
  • Niet online

Glimi

Designer Drugs

(overleden)
Verwijderd schreef op dinsdag 07 december 2004 @ 12:18:
Ik hoop niet dat dit verrassend was? Je overschijft de init functie in al je subclasses, dus de laatste functie die init overschrijft wordt aangeroepen. [...]
Waarom is het niet verrassend? Omdat je hier over een virtual method praat? Waarom doen sommige talen het dan niet? Ik vind het eigenlijk verrassend dat jij het zo voor granted aanneemt.

De parent wordt ge-instancieerd voordat het child dat is (eerste aanroep in de ctor is impliciet de parent ctor zonder argumenten). Dus om dan gebruik te maken van methodes die in het child overridden zijn, is vragen om problemen, omdat die gebruik kunnen maken van members die niet bestaan in de parent en dus nog niet ge-instancieerd is. Natuurlijk kun je dat opvangen met je parent::init() conventie call, maar dan ga je iets per conventie doen, wat de compiler ook had kunnen afdwingen en wat dus ook een zekerheid is als je gewoon ctors gebruikt ipv een init() methode.
Er is trouwens ook een metric voor opgenomen in Idea.
Maar, we dwalen af, heeft iemand nog een overtuigend argument om één van beide opties juist wel, of juist niet, te doen?
Ik vind het niet gegarandeerd correct ge-initialiseerd zijn bij een tweede generatie toch behoorlijk overtuigend.

[ Voor 6% gewijzigd door Glimi op 07-12-2004 13:13 ]


Acties:
  • 0 Henk 'm!

Verwijderd

Glimi schreef op dinsdag 07 december 2004 @ 13:12:
[...]

Waarom is het niet verrassend? Omdat je hier over een virtual method praat? Waarom doen sommige talen het dan niet? Ik vind het eigenlijk verrassend dat jij het zo voor granted aanneemt.

De parent wordt ge-instancieerd voordat het child dat is (eerste aanroep in de ctor is impliciet de parent ctor zonder argumenten). Dus om dan gebruik te maken van methodes die in het child overridden zijn, is vragen om problemen, omdat die gebruik kunnen maken van members die niet bestaan in de parent en dus nog niet ge-instancieerd is. Natuurlijk kun je dat opvangen met je parent::init() conventie call, maar dan ga je iets per conventie doen, wat de compiler ook had kunnen afdwingen en wat dus ook een zekerheid is als je gewoon ctors gebruikt ipv een init() methode.
Er is trouwens ook een metric voor opgenomen in Idea.

[...]
Ik vind het niet gegarandeerd correct ge-initialiseerd zijn bij een tweede generatie toch behoorlijk overtuigend.
Volgens mij wordt de parent niet eerst ge-instantieerd, maar juist de child-class. Vanuit de child-class worden eventueel parent functies aangeroepen, niet andersom. Als er geen child-constructor is maakt java bijvoorbeeld een constructor aan met de signature van de parent die vervolgens de parent aanroept, en dus niet andersom.

Acties:
  • 0 Henk 'm!

  • .oisyn
  • Registratie: September 2000
  • Laatst online: 03:42

.oisyn

Moderator Devschuur®

Demotivational Speaker

Verwijderd schreef op dinsdag 07 december 2004 @ 10:06:
De discussie is op zich niet taalspecifiek, beide situaties werken, beide situaties doen hetzelfde. Met dat uitgangspunt, onafhankelijk van de taal, kun je een uitspraak doen over wat beter of correcter is om bepaalde redenen. Ik denk zelf dat de tweede versie mooier is omdat je dan de parent constructor met rust laat, je hoeft je als implementator niet druk te maken om de functionaliteiten binnen die functie en je alleen richten op de uitbreiding ervan.
Maar je moet wel roeien met de riemen die je hebt, een init methode aanmaken in een derived class en die aanroepen vanuit de constructor van de base werkt gewoon niet in een aantal talen. Het gaat hier dan ook om een typische PHP approach, niet zozeer om een algemene OO aanpak. Vandaar dat ik PHP in de titel heb gezet.

Give a man a game and he'll have fun for a day. Teach a man to make games and he'll never have fun again.


Acties:
  • 0 Henk 'm!

  • Glimi
  • Registratie: Augustus 2000
  • Niet online

Glimi

Designer Drugs

(overleden)
Verwijderd schreef op dinsdag 07 december 2004 @ 13:15:
Volgens mij wordt de parent niet eerst ge-instantieerd, maar juist de child-class. Vanuit de child-class worden eventueel parent functies aangeroepen, niet andersom. Als er geen child-constructor is maakt java bijvoorbeeld een constructor aan met de signature van de parent die vervolgens de parent aanroept, en dus niet andersom.
Taal afhankelijk, ik zou niet weten wat PHP doet maar wat Java doet natuurlijk wel:
[...]This constructor does not begin with an explicit constructor invocation of another constructor in the same class (using this). If this constructor is for a class other than Object, then this constructor will begin with an explicit or implicit invocation of a superclass constructor (using super). Evaluate the arguments and process that superclass constructor invocation recursively using these same five steps. If that constructor invocation completes abruptly, then this procedure completes abruptly for the same reason. Otherwise, continue with step 4.
[...]
Unlike C++, the Java programming language does not specify altered rules for method dispatch during the creation of a new class instance. If methods are invoked that are overridden in subclasses in the object being initialized, then these overriding methods are used, even before the new object is completely initialized. Thus, compiling and running the example:
Java:
1
2
3
4
5
6
7
8
9
10
11
12
class Super {
    Super() { printThree(); }
    void printThree() { System.out.println("three"); }
}

    class Test extends Super { int three = (int)Math.PI; // That is, 3 
      public static void main(String[] args) { 
        Test t = new Test(); 
         t.printThree(); 
      } 
      void printThree() { System.out.println(three); } 
    } 

produces the output:
code:
1
2
0
3


This shows that the invocation of printThree in the constructor for class Super does not invoke the definition of printThree in class Super, but rather invokes the overriding definition of printThree in class Test. This method therefore runs before the field initializers of Test have been executed, which is why the first value output is 0, the default value to which the field three of Test is initialized. The later invocation of printThree in method main invokes the same definition of printThree, but by that point the initializer for instance variable three has been executed, and so the value 3 is printed.
Java zal alleen een constructor zonder parameters maken als je geen constructor definieert. Dit is gewoon een call naar de superconstructor. Dat de syntax is dat de parent door het child wordt aangeroepen zegt natuurlijk niet veel, maar in dit geval is dat wel waar. Het betekend dat eerst het parent object ge-initialiseerd wordt en dan pas het child object. Echter dat wil niet zeggen dat de ruimte voor het child object niet ge-alloceerd is

[ Voor 10% gewijzigd door Glimi op 07-12-2004 13:50 ]

Pagina: 1