[OOP] probleempje met overerving

Pagina: 1
Acties:

  • Genoil
  • Registratie: Maart 2000
  • Laatst online: 12-11-2023
ik heb jaren geleden wat van OOP geleerd met C++, en aangezien je daar multiple inheritance had had daar nooit het probleem waar ik nu tegenaan loop. ik programmeer nu in PHP5, maar ik denk dat mensen die Java kennen me ook wel goed kunnen helpen:

ik heb de volgende basis class-hierarchy:

code:
1
2
3
DomDocument
   +-Page
      +-Form


Page is een class die heel basic XSLT transforms doet, Form een uitbreiding daarop die basic form-handling ondersteunt. De output is dus HTML, het gaat immers over een website :)

Elke pagina die in de website zit is een uitbreiding van Page of Form. Nou zit er in Page een method Page::setup(), waar voor-de-site-specifieke parameters worden gezet voor de XSL-transform. Dus eigenlijk wil ik deze functie abstract of stub maken, en een speciale uitbreiding van Page maken die deze functie implementeert, bijvoorbeeld GNL_Page:

code:
1
2
3
4
DomDocument
   +-Page
      +-GNL_Page
      +-Form


maar die specifieke setup geldt natuurlijk ook voor alle formulieren:

code:
1
2
3
4
5
DomDocument
   +-Page
      +-GNL_Page
      +-Form
         +-GNL_Form


Nu heb ik dus 2 classes die de method setup() moeten implementeren. Dat kan natuurlijk wel ranzig met copy-paste, maar ik wil uiteraard dat die code maar op 1 plek staat...

In C++ had ik een class GNL_XSLSetup gemaakt en:
code:
1
2
GNL_Page extends Page, GNL_XSLSetup
GNL_Form extends Form, GNL_XSLSetup


gedaan. Maar dat kan met PHP5 natuurlijk niet. Ik dacht dat ik dat vast met een interface zou kunnen oplossen, maar ik mag blijkbaar helemaal geen code kloppen in methods van interfaces... (Interface function GNL_XSLSetup::setup() cannot contain body )

hoe los ik dit nou netjes op?

  • Eelke Spaak
  • Registratie: Juni 2001
  • Laatst online: 12-05 15:26

Eelke Spaak

- Vlad -

Als de setup voor alle subclasses van Form gaat gelden, waarom definiëer je setup() dan niet gewoon in Form zelf?

Als dat niet kan begrijp ik je verhaal niet helemaal denk ik en moet je wat duidelijker zijn :).

TheStreme - Share anything with anyone


  • Genoil
  • Registratie: Maart 2000
  • Laatst online: 12-11-2023
Eelke Spaak schreef op 22 september 2004 @ 14:38:
Als de setup voor alle subclasses van Form gaat gelden, waarom definiëer je setup() dan niet gewoon in Form zelf?

Als dat niet kan begrijp ik je verhaal niet helemaal denk ik en moet je wat duidelijker zijn :).
setup() geldt ook voor alle subclasses van Page ( dus alle pagina's zonder <form>),
maar ik wil niet dat Form die voor de "GNL"-site specifieke setup() heeft, dus kan setup() geen member zijn van Page.

[ Voor 8% gewijzigd door Genoil op 22-09-2004 14:44 ]


  • Eelke Spaak
  • Registratie: Juni 2001
  • Laatst online: 12-05 15:26

Eelke Spaak

- Vlad -

Genoil schreef op 22 september 2004 @ 14:43:
[...]


setup() geldt ook voor alle subclasses van Page ( dus alle pagina's zonder <form>),
maar ik wil niet dat Form die voor de "GNL"-site specifieke setup() heeft, dus kan setup() geen member zijn van Page.
Ik ben in principe een Java-man met maar wat basic begrip van PHP, dus als dit niet kan weet ik het ook niet, maar goed :) .

Je zou een interface XslSetup (met methode setup() ) kunnen definiëren, en dan voor de site een implementatie maken als 'GnlXslSetup implements XslSetup'. Als je dan ook nog een class XslSetupManager maakt met als methode een getXslSetup() die een XslSetup returnt, kan je in de properties van je applicatie het volgende zetten:

xsl.setup=GnlXslSetup

Je manager class moet dan in de getXslSetup() methode een instantie van de klasse maken die in de properties staat en die returnen. Zo heb je een implementatie-onafhankelijke toegang tot de XSL-setup functie, die in principe ook nog eens losstaat van Page.

TheStreme - Share anything with anyone


  • Genoil
  • Registratie: Maart 2000
  • Laatst online: 12-11-2023
bedoel je zoiets?

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
<?php

interface XslSetup {
    function setup();
}

class GNL_XslSetup implements XslSetup {
    function setup() {
        echo "GNL_XslSetup::setup();";
    }
}

class Page {
    var $xsl;
    
    function __construct() {
        $this->xsl = XslSetupManager::getXslSetup();
    }
    
    function run() {
        $this->xsl->setup();
    }
}

class Form extends Page {

}

class GNL_Page extends Page {

}

class GNL_Form extends Form {

}


class XslSetupManager {
    function getXslSetup() {
        return new GNL_XslSetup();
    }
}

$cPage = new GNL_Page();
$cPage->run();

$cForm = new GNL_Form();
$cForm->run();

?>


dit werkt inderdaad prima :) (behalve dat de Manager nu dusdanig geschrven is dat ie maar 1 setup kan teruggeven :)) dankjewel! is dit de standaard-workaround voor het gebrek aan multiple-inherintance? heel kort door de bocht vervang je een directe member-method dus door een member-object die je via een Manager vult met de juiste implementatie.

[ Voor 34% gewijzigd door Genoil op 22-09-2004 15:43 ]


  • Eelke Spaak
  • Registratie: Juni 2001
  • Laatst online: 12-05 15:26

Eelke Spaak

- Vlad -

Ja, dat is wel zo'n beetje wat ik bedoelde ja.
dit werkt inderdaad prima :) (behalve dat de Manager nu dusdanig geschrven is dat ie maar 1 setup kan teruggeven :)) dankjewel! is dit de standaard-workaround voor het gebrek aan multiple-inherintance? heel kort door de bocht vervang je een directe member-method dus door een member-object die je via een Manager vult met de juiste implementatie.
Nou deze manier van werken is natuurlijk veel mooier dan die vieze multiple inheritance :) . Je houdt implementatie-specifieke functies apart van de abstractere logica, en je definiëert interfaces zodat de logica-classes de configuratie op een algemene manier kunnen aanspreken. De manager class zorgt er dan voor dat de juiste implementatie wordt gebruikt.

TheStreme - Share anything with anyone


  • Genoil
  • Registratie: Maart 2000
  • Laatst online: 12-11-2023
ok, maar in principe werkt het het toch ook wel zonder die interface? ik begrijp alleen wel (eindelijk...na jaren de de klok hebben horen luiden) dat het goed is om ze te defineren, om zo een soort van communicateprotocol af te spreken tussen niet-gerelateerde objecten...toch?

  • Eelke Spaak
  • Registratie: Juni 2001
  • Laatst online: 12-05 15:26

Eelke Spaak

- Vlad -

Genoil schreef op 22 september 2004 @ 17:50:
ok, maar in principe werkt het het toch ook wel zonder die interface? ik begrijp alleen wel (eindelijk...na jaren de de klok hebben horen luiden) dat het goed is om ze te defineren, om zo een soort van communicateprotocol af te spreken tussen niet-gerelateerde objecten...toch?
Het werkt natuurlijk ook zonder de interface, maar je had het over GNL-sitespecifieke dingen, dus dan is het altijd goed om het abstract te definiëren zodat het herbruikbaar is.

Wat je zegt klopt dus :Y) .

TheStreme - Share anything with anyone


Verwijderd

Ik ben het er niet mee eens dat deze oplossing "mooier is dan multiple inheritance". Maar zonder dat heb je niet veel keus.

Dus dit is waarschijnlijk de beste.

Verwijderd

Wat we hier nu zien is niet een gevolg van troubles met MI, maar het hiden van basisfuncties in afgeleide klasses. De beste aanpak lijkt mij om nu je nog aan het ontwerpen bent andere method-namen te kiezen. Als je de ene PageSetup noemt, de ander FormSetup etc dan is het probleem ook opgelost.

Je kan ook blijven hiden, de fucntie in de Page class kan dan alsnog benaderd worden met:
code:
1
myGNL_Form.Page::Setup()


[quote] is dit de standaard-workaround voor het gebrek aan multiple-inherintance?[quote]
Containment is de standaard aanpak wanneer MI niet ondersteund wordt. Containment heeft ook weer zijn nadelen (meer code, minder efficiente uitvoer), als beide kunnen is het verstandig bewust te kiezen.

Maar stel je hebt MI: wanneer name clashes ontstaan door MI is er naast containment een andere mogelijkheid: de classes die problemen geven over te laten erven door hulp-structs (die wel inplementatie ondersteunen ittt een interface :+ ) en vervolgens van de structs te laten erven. Afhankelijk van of een object als structA of structB gecast is, wordt dan de juiste functie aangeroepen.

  • Genoil
  • Registratie: Maart 2000
  • Laatst online: 12-11-2023
err..klinkt ook plausibel (lees: :? ;)) maar ik ben eigenlijk wel tevreden met de huidge oplossing :). maar nu ik het zo gemaakt heb zie ik ineens dat ik eigenlijk veel beter iets met de XslProcessor class moet gaan doen. Die is nl. ook een member van Page. XslSetup::setup(Page p) doet eigenlijk niets anders dan xslt parameters zetten voor die processor, dus zou ik die moeten subclassen als bv. Processor, en setup(Page p) daar een overridable method van moeten maken. Een of andere PageManager moet dan de juiste Processor aan de juiste Page hangen.

Dat is dan een mooi voorbeeld van "containment" :)

[ Voor 3% gewijzigd door Genoil op 22-09-2004 22:51 ]


Verwijderd

Zoals je het nu beschijft begrijp ik dat een GNL_Form object maar over 1 XslSetup beschikt specifiek voor de GNL_Form-class en de XslSetup specifiek voor de basisclasses niet aanwezig is.

Door de verschillende XslSetup classes in een hierarchie te vatten kom ik uit bij het patroon "Factory Method". Op dit voorbeeld toegepast:
code:
1
2
3
4
BasisAbsXslSetup    ---+ DomDocument
<-PageXslSetup           <-Page 
<-FormXslSetup           <-Form
    <-GNL_FormXslSetup       <-GLN_Form


Als basis voor de koppeling tussen de class-hierarchie heb ik DomDocument gekozen. In deze basisclass zit een abstracte methode om het objecten van de tevens abstracte class XlsSetup te laden. Op deze manier werkt het laden van Page tot bv GNL_Form gelijk. Als DomDocument niet geschikt is om een abstracte methode in onder te brengen, zou ik hiervoor een aparte abstracte subclass introduceren tussen DomDocument en Page (XlsSetupAware bv). Op deze manier blijft Page "clean".

De relatie tussen de twee class-bomen is aggretatie/containment. Hierdoor valt dit ontwerp ook onder een ander patroon, Strategy. Deze maakt mogelijk dat on-the-fly te wisselen is van "methode" (XlsSetup), al wil je daar volgens mij geen gebruik van maken.

  • Genoil
  • Registratie: Maart 2000
  • Laatst online: 12-11-2023
hmm , niet helemaal denk ik. door de naamgeving wat te wijzigen wordt het hopelijk allemaal nog wat duidelijker. Ik ga dat hele "XslSetup" veranderen in "Template". Zo wordt het duidelijk dat het eigenlijk gewoon over "separation of concerns" gaat. Page en al z'n subclasses houden zich bezig met de content (een XML-document, gePOSTe formulieren etc.), en Template houd zich bezig met de stijl(een XSL document, de setup daarvan, de transform, etc.).
In de meeste gevallen heb je voor 1 site maar 1 template, dus iets als GNL_FormXslSetup aka GNL_FormTemplate zul je niet zo snel krijgen. Eerder:

voor stijl:
code:
1
2
3
4
5
XslProcessor
  +-Template
      +-GNL_Template
      +-GOT_Template
      +-TNET_Template


en voor de content:
code:
1
2
3
4
5
6
7
DomDocument
  +-Page
      +-GNL_IndexPage
      +-GNL_AnotherPage
      +-Form
         +-GNL_LoginForm
         +-GNL_AnotherForm


Template wordt dan een member van Page.

In Page moet dan een of andere Manager/Factory aangeroepen worden die de Template instelt. Die keuze is simpelweg een .conf/.ini setting, het is immerseen statisch gegeven voor 1 hele site.

Op die manier is het ook ineens duidelijk dat het gebrek aan MI helemaal niet zo erg is, Page en Template dienen conceptueel namelijk strict gescheiden te blijven.

[ Voor 43% gewijzigd door Genoil op 23-09-2004 11:24 ]


Verwijderd

Ik denk dat we het over hetzelfde hebben.

Qua structuur: Factory Method is algemener dan je wilt gebruiken:
a) het stelt niet verplicht dat elke class uit de "container" een eigen "contained" class heeft, het is vrij een "contained" class te kiezen uit de hele boom van "contained" classes (mits niet abstract ;) ).
b) het stelt niet verplicht dat het aantal contained objecten tot 1 per container beperkt blijft.

De class boom die je uittekent past in Factory Method:
Bij a): je class bomen zijn idd niet symmetrisch, maar da's dus ok
Bij b): hier wil je geen gebruik van maken, maar 1 contained object per container

Qua gedrag: Strategy blijft ook van toepassing. XlsProcessor en zijn afgeleide klasses van zie ik als presentatiestrategieen. Alleen zal van runtime wisselen waarschijnlijk geen gebruik gemaakt worden.
Pagina: 1