Continuo a vedere il seguente schema quando le persone imparano a conoscere OOP:
Problema: come posso inserire oggetti di tipi diversi ma correlati in un contenitore?
Soluzione: eredita da una classe base comune.
Nuovo problema: ho un sacco di oggetti di classe base, come posso ottenere il tipo reale?
Soluzione: ???
L'esempio di ereditarietà classica è di creare un programma di grafica che si occupa di oggetti geometrici. Abbiamo Circle
s, Ellipse
s e Triangle
s. Tutti questi oggetti hanno una base comune Drawable
.
InC++questopotrebbesembrarequalcosadeltipo:
structDrawable{virtual~Drawable(){}virtualvoiddraw()const=0;};structTriangle:Drawable{Pointp1,p2,p3;voiddraw()constoverride;voidstretch(doublexfactor,doubleyfactor);std::touple<double,double,double>getAngles()const;//...};structCircle:Drawable{doubleradius;voiddraw()constoverride;//...};structEllipse:Drawable{doubleradius;voiddraw()constoverride;//...};std::vector<Drawable*>drawables={newCircle,newTriangle,newEllipse};for(constauto&d:drawables)d->draw();
Finquituttobene.OravoglioaumentareilraggiodelmioCircle
seEllipse
s.Perquantonesocisono4modiperfarlo:
1:Mettituttonellaclassebase.LaclassebaseottienelefunzionivirtualigetAngles
,getRadius
,stretch
egetEccentricity
.Esisteun'implementazionepredefinitaperciascunainmodoche,adesempio,getRadius
restituiscaunvalorefittizioquandosihaachefareconunaTriangle
.Dopoaverlofatto,puoifacilmenteeseguireiterazionisuDrawable
seaumentareillororaggio.Questorendelaclassebaseincredibilmentegrandeelavoraresuunvector<Drawable*>
diventainefficienteperchélefunzioninonfannonullaperlamaggiorpartedeglielementi.
2:usadynamic_cast
hack.
for(constauto&d:drawables){if(dynamic_cast<Circle*>(d))((Circle*)d)->radius++;elseif(dynamic_cast<Ellipse*>(d))((Ellipse*)d)->radius++;}
Purtroppodeviripeterequestabruttasintassiognivoltachedevifarequalcosadelgenere.Nonpuoimetterloinunafunzioneoinunamacro.OgnivoltachevieneaggiuntounnuovoDrawable
,devipassareattraversol'interocodicebaseeaggiungerlodoveappropriato.Anchelaif
/else
-chainèabbastanzainefficiente.
3:aggiungiuntagcomeprimavariabilemembroeeseguiilcastdiconseguenza.
for(constauto&d:drawables){switch(d->tag){caseTag::Circle:((Circle*)d)->radius++;break;caseTag::Ellipse:((Ellipse*)d)->radius++;break;}}
Leggermentepiùefficientegrazieaunoswitchanzichéaif
/else
-chain,maesistegiàuntipoditagchemostrailtipodinamico,quindiiltagèridondante.Inoltrehalostessoproblemadi2:dalmomentocheènecessariomodificarel'interabasedicodiceognivoltachearrivaunnuovoDrawable
.
4:mantieniglioggettiincontenitoriseparati.
vector<Circle>circles;vector<Triangle>triangles;vector<Ellipse>ellipses;for(auto&c:circles)c.radius++;for(auto&e:ellipses)e.radius++;
Questoèunlayoutdimemoriamoltoefficienteesiiterasolosuoggettichecontano.Èilmiopreferito,masfortunatamenterompelaprimasoluzioneinquantononsiconservanooggettidigitatiinmododiversoinunsingolocontenitore.
Hoprovatoarisolvereilproblemacreandounmodellovariadiccheinternamentehavector
pertipoeinoltrainserimentinelcontenitorecorretto.Idealmentenasconderebbecompletamenteilfattochecisonodiversicontenitorisottoilcofano.L'utilizzosarebbesimileaquesto:
MultiVector<Triangle,Circle,Ellipse>mv;mv.push_back(Triangle());mv.push_back(Circle());mv.push_back(Ellipse());for(auto&d:mv.get<Circle,Ellipse>())d.radius++;
Lasoluzionenonèperfetta,adesempioidiversitipinonmantengonoilloroordineel'implementazionedell'iterazioneècomplicata.
Hopresentatoquestoproblemaeiltentativodisoluzionealmiogruppodiricerca(universitario)ehoottenutolereazioni" Se progetti le tue classi correttamente questo non è un problema " e " La soluzione proposta è uno strumento molto specifico per risolvere un problema molto specifico ed è discutibile nel migliore dei casi aggiungere ancora più complessità a c ++ per un dettaglio così piccolo ". A mio parere, questo è un problema molto basilare che deve essere risolto e la solita soluzione di design di classe migliore cerca solo di trovare il meno male dei 4 workaround nel caso specifico senza effettivamente risolvere il problema. Il problema si presenta quasi ogni volta che viene utilizzata l'ereditarietà.
Quindi finalmente ecco la domanda: hai incontrato anche questo problema / questo è un vero problema? Sai di un'altra soluzione / soluzione? Altre lingue affrontano meglio il problema? Voglio simulare un sondaggio usando 2 commenti qui sotto, quindi sentiti libero di scegliere " Non ho mai avuto questo problema / Il problema è stato risolto facilmente. " o " Ho anche avuto questo problema e non ho mai risolto per la mia soddisfazione. "
Modifica: per evitare una chiusura dovuta principalmente a opinioni / discussioni: voglio una risposta chiara che risolva il problema o una risorsa che dice che non ce n'è.
Modifica: correlati: Ho davvero un'auto in il mio garage?