[C++] Gedeelde klasses voor datauitwisseling + overerving

Pagina: 1
Acties:

  • DieterVDW
  • Registratie: Juli 2002
  • Laatst online: 12-02-2017
Hallo,

Ik zit met een programmatorisch probleempje.
De situatie is als volgt: ik en een andere programmeur ontwikkelen samen een applicatie.
Het deel dat ik ontwikkel en het deel dat hij ontwikkelt zijn perfect gescheiden, hij maakt enkel nu en dan een aanroep naar mijn deel om data door te geven. Nu hebben we de structuur van die klassen die we gebruiken om de data door te geven reeds afgesproken en in headerfiles gezet.
Bij de ontwikkeling van mijn deel zou het echter heel erg handig zijn om functionaliteit toe te voegen aan die klassen. Ik wil echter niet dat ik de methodes die ik toevoeg aan de klassen terecht komen in de gedeelde headerfiles.
Ik dacht dan om overerving te gebruiken. De gedeelde klassen bevatten enkel de declaraties van de data die uitgewisseld moet worden. We maken dan elk afgeleide klassen die onze implementatie specifieke methodes en extra variabelen bevatten. Door de afgeleide klasse terug te casten naar de basisklasse (die enkel de data bevat en niks meer), kunnen we de data simpel uitwisselen tussen de delen.
Aan mijn kant kan ik door wat pointer magic die data dan in mijn afgeleide klasse krijgen.
De gedeelde headerfiles blijven dan mooi sober, en alle implementatie specifieke zaken bevinden zich in de bestanden van de uitgebreide klasse.

Erg leuk in theorie, maar het probleem is dat een klasse ook een andere klasse als attribuut kan hebben, en dat maakt het allemaal erg ingewikkeld ...

Mijn idee was dus:
code:
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
/*
 *  De sobere klasse (enkel datacontainer eigenlijk) die gebruikt wordt om data 
 *  uit te wisselen tussen de twee delen.
 */
class Basisklasse
{
public:
   int data;
}

/*
 * De uitgebreide klasse die extra functionaliteit/attributen bevat voor het
 * gebruik binnen mijn deel.
 */
class Uitgebreide_klasse : public Basisklasse
{
public:
   Uitgebreide_klasse();
   Uitgebreide_klasse(Basisklasse b)     // Cast constructor
    {
        *((Basisklasse*)this) = b;  // Pointer magic
    };
   ~Uitgebreide_klasse();

    int extra_data;
    void extra_methode();
    ...
}


Als je nu het volgende hebt:
code:
1
2
3
4
5
6
class Basisklasse
{
public:
   int data;
    Tweede_basisklasse bla;
}

Dan wordt het al héél wat ingewikkelder en viezer ...
Moet je dan in de uitgebreide klasse een 2de attribuut bla2 aanmaken of zo van de 2de uitgebreide klasse? Pff ...

Ik ben allicht niet goed bezig en kan wel wat advies gebruiken over hoe deze situatie het beste aan te pakken ... Alle advies is welkom.

[ Voor 9% gewijzigd door DieterVDW op 14-03-2006 16:31 ]


  • NMe
  • Registratie: Februari 2004
  • Laatst online: 22-01 23:51

NMe

Quia Ego Sic Dico.

Misschien kun je iets met het interface pattern of het facade pattern. Wikipedia is daar echter vrij beknopt over, dus misschien heb je meer aan Google. :)

'E's fighting in there!' he stuttered, grabbing the captain's arm.
'All by himself?' said the captain.
'No, with everyone!' shouted Nobby, hopping from one foot to the other.


  • .oisyn
  • Registratie: September 2000
  • Laatst online: 20:20

.oisyn

Moderator Devschuur®

Demotivational Speaker

Ik snap niet waarom je de overerving wilt laten plaatsvinden op de data. Die data wordt gewoon sec verstuurd, het is dan ook niet echt logisch om er aan de andere kant ineens een ander type van te maken. Als je extra data wilt opslaan kun je er net zo goed voor kiezen om een klasse te maken die dat gesharede datatype als member heeft, en daarnaast nog overige datamembers.

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.


  • DieterVDW
  • Registratie: Juli 2002
  • Laatst online: 12-02-2017
Hmm ja hetgeen ik nu doe heeft eigenlijk wel sterke overeenkomsten met interfaces.
Ik dacht echter niet meteen aan interfaces omdat die toch vooral dienen om methodes te definiëren / functionaliteit vast te leggen. Maar in C++ is het wel mogelijk om interfaces ook te (mis)(ge)bruiken om een soort data-uitwisselingsformaat vast te leggen veronderstel ik ...
Ik doe nog even verder op dit spoor ...

Het façade pattern is een goeie suggestie, maar hier om praktische redenen niet zo handig.
Ik heb namelijk reeds beschikking over klassen die zowel data als helper methodes bevatten.
Ik werk dus eigenlijk 'omgekeerd': de uitgebreide klassen heb ik al, ik moet nu de uitwisselingsklassen opstellen ...
Om het façade pattern te gebruiken zou ik zowat al de bestaande methodes moeten aanpassen, wat ik dus probeer te vermijden!

Edit: @.oisyn: om bovenstaande reden dus. Anders moet ik in alle bestaande methodes telkens een attribuut gerefereerd wordt, er een verwijzing naar dat attribuut tussenplakken ...
Het is ook niet zo handig als de data dan nog een niveau dieper zit, aangezien die data ongelofelijk veel gebruikt wordt.

Omtrent je opmerking dat de data toch gewoon 'sec' doorgestuurd wordt:
Ik speelde ook nog even met het idee om een cleane sobere versie van de headerfiles te gebruiken,
en dan aan elke zijde een uitgebreide versie met de nodige methodes.
Elke kant gebruikt dan zijn uitgebreide versie van de headerfiles, en bij het uitwisselen wordt er gewoon een smerige cast uitgevoerd. Zolang de datadefinities hetzelfde blijven zou dit moeten werken ...
Maar als er dan verschillende attributen bijkomen aan beide kanten geeft dat allicht toch wel problemen... En het is sowieso ook niet de meest elegante / bugproof manier natuurlijk ...

[ Voor 40% gewijzigd door DieterVDW op 14-03-2006 17:31 ]


  • whoami
  • Registratie: December 2000
  • Laatst online: 17:32
Met oisyn eigenlijk.
Wat jij wilt is een Data Transfer Object dat op beide kanten gekend is.

Wat je dan gewoon moet doen, is dat de classes die dat object nodig hebben, ermee overweg kunnen.

https://fgheysels.github.io/


  • DieterVDW
  • Registratie: Juli 2002
  • Laatst online: 12-02-2017
whoami schreef op dinsdag 14 maart 2006 @ 17:30:
Met oisyn eigenlijk.
Wat jij wilt is een Data Transfer Object dat op beide kanten gekend is.

Wat je dan gewoon moet doen, is dat de classes die dat object nodig hebben, ermee overweg kunnen.
Stel nu dat ik dat doe, dan heb ik nu twee opties:
A ) Ik voeg een soort casting constructor/methode toe aan de reeds bestaande uitgebreide klasse die de data van het Data Transfer Object overzet in de uitgebreide klasse.
Dit komt neer op heel wat statements in de vorm van:
this->data = bla.data;

of

B ) Ik voeg het Data Transfer Object toe als attribuut in mijn uitgebreide klasse en verwijder alle overeenkomstige attributen uit de uitgebreide klasse.
Dan moet ik in elke methode elke referentie naar data vervangen door klasse.data ...

Het probleem is natuurlijk dat ik reeds met die bestaande implementatie zit.
Beide oplossingen zijn nogal lomp vind ik.

Vandaar dat ik iets probeer te forceren adhv overerving en een smerige cast, zodat ik de aanpassingen aan de bestaande uitgebreide klasse kan beperken ...
Of denken jullie dat dit geen werkbare oplossing kan geven?

Iedere oplossing die ik kan bedenken is bijzonder omslachtig / onelegant, vraag is nu: welke van de voorgestelde oplossingen is het minst erg? :s

[ Voor 13% gewijzigd door DieterVDW op 14-03-2006 18:01 ]


  • .oisyn
  • Registratie: September 2000
  • Laatst online: 20:20

.oisyn

Moderator Devschuur®

Demotivational Speaker

DieterVDW schreef op dinsdag 14 maart 2006 @ 17:25:
Edit: @.oisyn: om bovenstaande reden dus. Anders moet ik in alle bestaande methodes telkens een attribuut gerefereerd wordt, er een verwijzing naar dat attribuut tussenplakken ...
Het is ook niet zo handig als de data dan nog een niveau dieper zit, aangezien die data ongelofelijk veel gebruikt wordt.
Euh :?
Dus je hebt eigenlijk ook methodes in die class, die zowel jij als je collega apart van elkaar implementeren in jullie eigen deelprojecten, en waarvan de code ook anders is omdat ze ook die extra datamembers moeten accessen? Als dat zo is klopt je design niet, en als dat niet zo is snap ik je opmerking niet helemaal :).

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.


  • DieterVDW
  • Registratie: Juli 2002
  • Laatst online: 12-02-2017
Ok ik zal het proberen verduidelijken :) .

De uitgebreide klassen bestaan al in een bestaand project.
Deze klassen bevatten de data die moet overgebracht worden + nog extra hulpattributen en heel wat methodes om de data in deze klassen te verwerken.
Om het extra gecompliceerd te maken zijn die klassen hiërarchisch van elkaar afhankelijk, klasse a is attribuut van klasse b etc etc . En de bestaande methodes zijn nogal afhankelijk van die hiërarchie.

De andere programmeur moet mij de data voor in deze klassen bezorgen.
Het probleem is nu dat die andere programmeur helemaal niks te maken heeft met al de extra hulpattributen en methodes in de uitgebreide klassen.
Deze wil enkel een soort datacontainer klasse die 'm kan invullen en doorspelen aan mij.
Geen methodes of hulpattributen dus aan zijn kant, enkel de core data-attributen.

Hij wil dus light klassen zonder functionaliteit waar 'm gewoon z'n data in kan ploffen,
ik wil heavy klassen met al de extra functionaliteit aangezien ik die data moet verwerken.

De light klassen heb ik nu gemaakt door gewoon de headerfiles van de heavy klassen te nemen en daar alle methodes en hulpattributen uit te slopen. Dan krijg je effectief een light datacontainerklasse die ideaal is voor de andere programmeur.

Ik zou deze light klassen die ik dan krijg willen omtoveren in de heavy klassen, zodat ik daar makkelijk kan mee werken in mijn deel.

Ik zou natuurlijk gewoon de header files van de heavy klassen kunnen geven aan de andere programmeur, maar dan krijgt deze heel wat methodes en attributen te zien waar 'm helemaal niks mee te maken heeft, en wat voor 'm alleen maar verwarrend is.
Stel dat deze programmeur dan nog wat functionaliteit wil toevoegen aan de klassen in het kader van zijn deel, dan komt dit ook allemaal in de gemeenschappelijke headerfiles. En dan heb ik helemaal niks te maken met de methodes/attributen die hij toevoegt...

Wat ik wou bereiken met overerving is het volgende:
We hebben gemeenschappelijk de headerfiles van de light klassen, en gebruiken deze om de data uit te wisselen. Stel dat de andere programmeur functionaliteit wil toevoegen, dan maakt 'm gewoon een eigen heavy klasse die overerft van de light klasse. Aangezien de light klasse alle nodige data bevat kan 'm die heavy klasse gewoon casten naar de light klasse en die doorspelen aan mij.
Met wat pointer magic kan ik die light klasse dan omvormen tot mijn heavy klasse die overeft van de light klasse. Daarmee doe ik dan precies wat ik wil.

Het probleem is de hiërarchie die in de klassen zit, die gooit nogal wat roet in het eten...

Ik veronderstel wel dat dit design niet echt foutloos is momenteel, maar ik voel me wat verloren omtrent de richting die ik moet uitgaan ...

Het façade pattern lijkt me qua design het meest aangewezen: de light klasse is dan een attribuut van de heavy klasse. Maar ook hier smijt die hiërarchie weer roet in het eten.
Stel dat ik bvb een light klasse A krijg die een vector met elementen van light klasse B bevat.
Dan wijs ik klasse A toe als attribuut aan de heavy klasse AA, maar wat doe je dan met die vector van B's ? In de heavy klasse AA een overeenkomstige vector maken van heavy klassen BB die dan elk als attribuut de overeenkomstige B hebben? Hmm ...
(Is dit verstaanbaar?)

PS: Even vraagje terzijde: kun je in een overervende klasse een attribuut hebben met dezelfde naam als in de ouderklasse maar ander type? Ik denk van wel, het nieuwe attribuut zit dan in een 'hogere' scope en het oude is onzichtbaar? Dit zou icm die overerving misschien iets kunnen geven ...

PPS: Sorry dat ik steeds heavy en light gebruik, het is niet echt correct maar je snapt wel wat ik bedoel denk ik ...

  • .oisyn
  • Registratie: September 2000
  • Laatst online: 20:20

.oisyn

Moderator Devschuur®

Demotivational Speaker

Wat je wilt is een 'interface' (in de ruimste zin van het woord, niet per se een stricte OO interface) waar iemand anders mee werkt om z'n data aan jou te geven. Die ander is idd niet geïnteresseerd in de inner workings van jouw code, hij wil alleen z'n data aan jou kunnen geven.

Die "light klasse" waar je het over hebt voldoet daar idd wel aan, maar imho ga jij daar vervolgens verkeerd mee om. Je probeert functionaliteit toe te voegen aan iets wat gewoon bedoelt is om data in op te slaan. Het lijkt me dan ook handiger om de functionaliteit er omheen te schrijven ipv eraan toe te voegen. Dat je dan vervolgens een extra niveau van indirectie krijgt (je moet 'data.member' gebruiken ipv gewoon 'member') is een inconvenience maar niet onoverkomelijk. Je ontwerp wordt er wel een stuk beter door.

Overigens gaat dit allemaal veel meer over ontwerp en past je topic derhalve beter in het nieuwe forum Software Engineering & Architecture, dus ik verplaats 'm even :)

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.


  • matthijsln
  • Registratie: Augustus 2002
  • Nu online
Om nou allemaal trukendozen open te trekken omdat wat members en member functions verwarrend zijn lijkt me nogal een vreemde werkwijze. Kan je de code gewoon niet wat minder verwarrend maken, bijvoorbeeld door wat commentaar of documentatie?

Wil je het toch scheiden zijn interfaces (interface = class met alleen maar abstracte member functions) inderdaad een goede oplossing.

Daarnaast zijn jullie misschien gebaat bij het gebruik van een versiebeheersysteem zoals Subversion. Dit soort systemen zijn ervoor gemaakt om samen aan één codebase te kunnen werken.

  • Cuball
  • Registratie: Mei 2002
  • Laatst online: 15:59
ik zou compositie gebruiken ipv lelijk te doen met overerving, je kan toch gewoon en "Light" klasse in je "Heavy" klasse steken en dan enkele methodes schrijven in je "Heavy" klasse die de "Light" klasse aanspreken, dit natuurlijk wel allemaal tegen een Interface...

"Live as if you were to die tomorrow. Learn as if you were to live forever"


  • DieterVDW
  • Registratie: Juli 2002
  • Laatst online: 12-02-2017
Wel ik heb dus toch nog verdergedaan op dit spoor, kwestie van te ontdekken of het mis zou gaan.
Ik moet zeggen dat het nog altijd goed uitdraait.
Het mag dan een smerige truuk zijn, en niet bepaald geheugen efficiënt, maar het werkt wel perfect.
Ik kan nu de bestaande uitgebreide klassen gebruiken met slechts minieme aanpassingen.
Super!
Pagina: 1