Newsgroup: it.comp.lang.c++
Titolo: classe astratta e derivate .....

Avrei un quesito da porvi,
data una struttura di classi di questo tipo :
class A
{
    virtual void DoAction() = 0;
    virtual ~A(){};
};
class B : public A
{
    virtual DoAction(){ // .... };
    virtual ~B(){};
private :
    MY_STRUCT_b my_struct_b;
};
class C : public A
{
    virtual DoAction(){ // .... };
    virtual ~C(){};
private :
    MY_STRUCT_C my_struct_c;
};
// [...]
// Get ritorna un puntatore a B o C.
A *a = Get();
a->DoAction();
// [...]
Se in questo punto avrei bisogno dei dati membro di C o B (per
inviarli su socket, ad esempio) quale sarebbe l'approccio
più giusto per voi ? Quello che farei io è una funzione Send
nella classe astratta A che invii questi dati. Voi che fareste ?
E se avessi necessariamente bisogno dei dati di C e B ?
Quale è la cosa più pulita in C++ ?
grazie a chi voglia rispondermi.
_ _
Freedom.

 Quale č la cosa piů pulita in C++ ?
Metti qualcosa che consenta la reflection e che ti permetta il casting 
sicuro. Al limite lasci al compilatore il compito di fare il controllo e fai 
un dynamic_cast, controllando se il puntatore ritorna 0 o un puntatore 
valido.
-- 
Costantino Grana 

> Quale è la cosa più pulita in C++ ?


valido.
Eeh ?! ma perché forzare un downcast ?... non mi sembra sia il caso...

   // if ((*aList)->GetType() == B)
Dal mio punto di vista è un problema di design.  La classe A definisce
un'interfaccia, la funzione virtuale DoAction() compie l'azione
specifica.  Non dovresti avere bisogno di esaminare a run-time il tipo
effettivo dell'oggetto nella lista per compiere operazioni ordinarie.
Inquadra bene l'interfaccia a livello astratto e realizza i
comportamenti mediante le sole funzioni virtuali.
Che cosa ti serve fare nello specifico ? (magari capendo l'esigenza, a
grandi linee, ti si può aiutare meglio...)

 Eeh ?! ma perché forzare un downcast ?... non mi sembra sia il caso...
Dipende dal problema. Non avendo dettagli, non si può dire. Ad esempio io 
avevo una gerarchia di feature visuali da calcolare su un set di immagini. 
Tutto gestito tramite la stessa interfaccia senza problemi, però ad un certo 
punto per consentire di visualizzare una di queste (che era sostanzialmente 
una versione ridotta dell'immagine di partenza) a scopo di debug, la 
soluzione più logica è stata fare il dynamic_cast e utilizzare i membri 
interni della classe. L'alternativa sarebbe satata quella di modificare 
l'interfaccia per una necessità temporanea e specifica per un solo caso.
-- 
Costantino Grana 

 grandi linee, ti si puň aiutare meglio...)

 l'interfaccia per una necessità temporanea e specifica per un solo caso.
"A scopo di debug" e "necessità temporanea", in questi periodi, sono
espressioni che scriverei a caratteri cubitali ;-)
Io ragione sempre in termini di codice di produzione, se per scopi di
debug, invece, si può sorvolare su certe regole, in via temporanea.

 al motore nella classe.
Mi stai dicendo che "siccome il motore è bello complesso", allora è
bene sorvolare su regole di design ?
Io giustifico l'analisi a run-time di ciò che è effettivamente
contenuto in un oggetto, solo se stai scrivendo un motore di
persistenza, altrimenti non capisco la reale necessità (magari è
ignoranza mia, eh?).

 dovrei proprio arrivarci. O sbaglio ?
Sono d'accordo, non dovresti arrivarci. Dovresti invece cercare di
separare i contesti dove ti serve considerare i discendenti di A
uniformi a runtime da quelli dove hai bisogno di operare espressamente
sui loro tipi. I primi li gestisci tramite funzioni virtuali, mentre
per i secondi il modo per fattorizzare il codice comune e' dato dai
template.
Detto questo esistono comunque situazioni dove non puoi fare a meno di
tenere comportamenti diversi in funzione del tipo dinamico di
un'oggetto. Ad esempio all'interno di una factory quando si deve
scegliere di creare un oggetto di una tra le sottoclassi di una classe
base data in funzione di condizioni esterne o nell'implementazione di
meccanismi di persistenza e serializzazione (che mi pare sia proprio
il tuo caso).
Ritornando al tuo caso direi che la cosa piu' importante e' verificare
se il tuo vettore di classi base e' lo strumento giusto per passare i
tuoi oggetti al protocollo di serializzazione. Puo' benissimo darsi
che la risposta sia si.
Ciao,
Nicola

 Quale è la cosa più pulita in C++ ?
Pattern Memento? E' su Design Patterns della GoF.
Intent: Without violating encapsulation, capture and externalize an
object's internal state so that the object can be restored to this
state later.
Mi sembra porprio ciò che fa al caso tuo.
Saluti,
Marcello.

 meccanismi di persistenza e serializzazione (che mi pare sia proprio il tuo caso).
Qui sono d'accordo solo in parte. La serializzazione può essere
tranquillamente fatta senza type-switching (per es. vedi metodo
virtuale serialize() di Java oppure pattern Memento della GoF). Per
quanto riguarda la persistenza, il type-switching può comunque essere
evitato. Se vuoi che l'oggetto venga semplicemente reso persistente lo
potresti mettere in un Blob dopo averlo serializzato. E quindi
ritorniamo al caso precedente.
Nel caso in cui invece vuoi che l'oggetto venga prima
"spacchettato" (per esempio per non renderlo opaco al database),
allora _puoi_ passare per il type switching. Ma puoi anche usare una
qualche forma di reflection basata sul polimorfismo.
Saluti,
Marcello.

 potresti mettere in un Blob dopo averlo serializzato.
In che modo ?

 virtuale serialize() di Java oppure pattern Memento della GoF).
Trovo l'attribuzione a tappeto alle classi della capacita' di
serializzarsi/deserializzarsi un errore architetturale che causa un
indebito accavallarsi di responsabilita'; un conto e' avere un ruolo
nella rappresentazione di un dominio applicativo, un conto e'
implementare un meccanismo puramente "di servizio" quale il
marshalling.
Non resta quindi che l'introspezione, interna o esterna al linguaggio
che sia, ovvero il type switching portato alle sue piu' estreme
conseguenze :-)
Ciao,
Nicola

 costruttore virtuale:
Si, ma se per avere i costruttori virtuali devo rinunciare almeno in
parte alla solidita' del meccanismo di costruzione del C++, preferisco
fare a meno dei costruttori virtuali e tenermi le mie factory.
Ciao,
Nicola

 fare a meno dei costruttori virtuali e tenermi le mie factory.
Sì, sì, citavo la differenza per la cronaca. In ogni caso, Delphi ha
un object model abbastanza più semplice di C++.  Non esistono, tanto
per fare un esempio, istanze statiche di una classe, non esiste
ereditarietà multipla, programmazione generica, ecc...

 In che modo ?
Facendolo discendere da un'interfaccia ISerializable, di cui
implementa il metodo virtuale serialize(). Dunque non è neanche
necessario che tale metodo stia alla base di un'ipotetica gerarchia
unica (per es.: TObject), che come dice Nicola è sintomo di un design
non proprio cristallino.
Saluti,
marcello.

 non proprio cristallino.
In realtà, TObject, in Delphi, non è serializzabile, mentre
TPersistent, che deriva da TObject, sì.
Quindi da questo punto di vista, la gerarchia è un po' più pulita.  A
me sembra che Nicola si riferisse al fatto che il meccanismo di
serializzazione è delegato alla classe stessa e non è un processo
"esterno" alla classe.

 "esterno" alla classe.
Ed è proprio quello che il memento suggerisce di fare per
esternalizzare lo stato pur senza violare l'incapsulamento.
Ciao,
Marcello.

Discussione molto interressante.
Ci sono situazioni, ditemi se non siete daccordo, in cui
non si piò fare a meno di conoscere lo stato interno di un oggetto. E' 
corretto
o c'è qualche cosa che non va nel design ? (la seconda che ho detto secondo 
me)
Proprio questo è il punto, cosa fareste voi ?
Per esempio :
ho la classe FRUTTA (astratta) e le classi MELA, PERA, FRAGOLA (derivate da
FRUTTA). Ho un insieme di FRUTTA
std::list <FRUTTA*> _fruit;
<produttore>
// Qualcuno riempe questa list .....
<consumatore>
Scorro la lista per riempire un messaggio (per esempio) e diciamo che ho un 
messaggio
per ogni tipo di frutta. In questo caso sono obbligato (anche se in un solo 
punto) a fare
qualche down cast o sbaglio ?
c'è il modo di evitare ciò secondo voi ?
thanks.

 qualche down cast o sbaglio ?
In questo caso particolare potresti definire il metodo virtuale 
GeneraMessaggio, che ogni classe derivata specializza per il suo tipo.
-- 
Costantino Grana 

qualche down cast o sbaglio ?
Non sei obbligato: deleghi ad ogni classe specializzata la generazione
del proprio messaggio, mediante l'override di una funzione virtuale
pura definita in FRUTTO (io la classe madre non la chiamerei FRUTTA,
se le derivate sono MELA, PERA, BANANA, ma FRUTTO).
Aspetto che esperti del calibro di Nicola confermino o smentiscano :-)

 c'è il modo di evitare ciò secondo voi ?
Assolutamente. Questo e' l'esempio canonico del pattern Visitor:
http://en.wikipedia.org/wiki/Visitor_pattern
Ciao,
Nicola


http://en.wikipedia.org/wiki/Visitor_pattern
Nicola, prendo spunto dal link per fare un esercizio, spero che tu mi
dia una mano a capire se ci sono errori.
Io ho pensato di applicare il pattern al caso concreto in questione e
quindi pensavo a una classe astratta per il visitor fatta cosě:
<CODE>
class Frutteto;
class Arancio;
class Arancia;
class Melo;
class Mela;
class Banano;
class Banana;
class Visitor {
public:
   virtual void visit(const Frutteto*) = 0;
   virtual void visit(const Arancio*) = 0;
   virtual void visit(const Arancia*) = 0;
   virtual void visit(const Melo*) = 0;
   virtual void visit(const Mela*) = 0;
   virtual void visit(const Banano*) = 0;
   virtual void visit(const Banana*) = 0;
};
</CODE>
E a una classe astratta Visitable seguente:
<CODE>
class Visitable {
public:
   virtual void accept(Visitor* visitor) const = 0;
};
</CODE>
Uso i puntatori per via dei metodi virtuali astratti, č corretto?
Supponendo a questo punto di avere la classe:
<CODE>
class Frutto: public Visitable {
protected:
   std::string color;
public:
   std::string getColor() const { return color; }   
   virtual void accept(Visitor* visitor) const = 0;
};
</CODE>
e di ereditare da essa le seguenti:
<CODE>
class Arancia: public Frutto {
public:
   Arancia() { 
     int c=rand()%100;        
     if (c < 33) 
       color="light orange";
     else
     if (c < 66) 
       color="orange";
     else
       color="dark orange"; 
   }
   void accept(Visitor* visitor) const { 
      visitor->visit(this); 
   }          
};
class Mela: public Frutto {
public:      
   Mela() { 
     int c = rand()%100;
     if (c < 50) 
       color="green"; 
     else
       color="red";
   }
   void accept(Visitor* visitor) const { 
      visitor->visit(this); 
   }          
};
class Banana: public Frutto {
public:
   Banana() { 
     int c = rand()%100;
     if (c < 33)
       color="yellow"; 
     else
     if (c < 66) 
       color="brown";
     else
       color="green";
   }
   void accept(Visitor* visitor) const { 
      visitor->visit(this); 
   }          
};
</CODE>
Alla stesso modo:
<CODE>
class Albero: public Visitable {
private:
  unsigned height;
  std::vector<Frutto*> fruttoVec;
protected:
  void _accept(Visitor* visitor) const {
     for (std::vector<Frutto*>::const_iterator it=fruttoVec.begin();
          it != fruttoVec.end();
          it++) {
        (*it)->accept(visitor);
     }
  }
public:
  virtual Frutto* createFruit() const = 0;
 
  unsigned getHeight() const { return height; }
  Albero() { 
    height = rand()%3+2; 
  }
 
  void spring() {
    for (int i=0; i<rand()%5; i++) {
        fruttoVec.push_back(createFruit());
    }       
  }
};
class Melo: public Albero {
public:
  Frutto* createFruit() const {
     return new Mela();   
  }    
   void accept(Visitor* visitor) const { 
      visitor->visit(this); 
      _accept(visitor);
   }    
};
class Banano: public Albero {
public:
 Frutto* createFruit() const {
     return new Banana();   
  } 
   void accept(Visitor* visitor) const { 
      visitor->visit(this); 
      _accept(visitor);
   }       
};
class Arancio: public Albero {
public:
  Frutto* createFruit() const {
     return new Arancia();   
  } 
   void accept(Visitor* visitor) const { 
      visitor->visit(this); 
      _accept(visitor);
   }             
};
</CODE>
A questo punto definisco il contenitore generale:
<CODE>
class Frutteto: public Visitable {
private:
  std::vector<Albero*> alberoVec;
public:
  Frutteto() {
    for (int i=0; i<rand()%5; i++)
      alberoVec.push_back(new Melo());         
    for (int i=0; i<rand()%5; i++)
      alberoVec.push_back(new Arancio());         
    for (int i=0; i<rand()%5; i++)
      alberoVec.push_back(new Banano());
  };       
 
  size_t getTreeCount() const {
      return alberoVec.size();
  }
 
  void spring() {
    for (std::vector<Albero*>::iterator it = alberoVec.begin(); 
         it != alberoVec.end();
         it++) {
      (*it)->spring();
    }       
  }
 
  void accept(Visitor* visitor) const { 
    visitor->visit(this);
    for (std::vector<Albero*>::const_iterator it = alberoVec.begin(); 
         it != alberoVec.end();
         it++) {
       (*it)->accept(visitor);  
    }
  }                        
};
</CODE>
E il visitor fatto a modo mio:
<CODE>
class PrintVisitor: public Visitor {
public:
   void visit(Arancio const* A) { 
        std::cout << "<arancio height=\"" 
                  << A->getHeight() 
                  << "\">" << std::endl; 
   }
 
   void visit(const Melo* M) {
        std::cout << "<melo height=\""
                  << M->getHeight() 
                  << "\">" << std::endl;         
   }
 
   void visit(const Banano* B) {
        std::cout << "<banano height=\""
                  << B->getHeight() 
                  << "\">" << std::endl;                 
   }   
 
   void visit(const Arancia* a) { 
        std::cout << "<arancia color=\""
                  << a->getColor() 
                  << "\"/>" << std::endl; 
   }      
 
   void visit(const Mela* m) {
        std::cout << "<mela color=\""
                  << m->getColor() 
                  << "\"/>" << std::endl; 
   }
   void visit(const Banana* b) {
        std::cout << "<banana color=\""
                  << b->getColor() 
                  << "\"/>" << std::endl;         
   }
 
   void visit(const Frutteto* F) {
        std::cout << "<frutteto treeCount=\""
                  << F->getTreeCount()
                  << "\">" << std::endl;                 
   }
};
</CODE>
con un main() del genere:
<CODE>
int main()
{
    srand(time(0));
    Frutteto frutteto;
 
    frutteto.spring();
    PrintVisitor pv;
    frutteto.accept(&pv);
    return EXIT_SUCCESS;
}
</CODE>
ottengo (per esempio) il seguente output:
<frutteto treeCount="6">
 <melo height="2">
  <mela color="red"/>
  <mela color="red"/>
  <mela color="red"/>
 <melo height="2">
  <mela color="red"/>
  <mela color="green"/>
 <melo height="3">
  <mela color="red"/>
  <mela color="green"/>
 <arancio height="3">
  <arancia color="light orange"/>
  <arancia color="orange"/>
  <arancia color="dark orange"/>
 <arancio height="2">
  <arancia color="dark orange"/>
  <arancia color="dark orange"/>
 <banano height="2">
  <banana color="brown"/>
Ora, a parte le new fatte su certi oggetti come gli alberi (mannaggia
alle abitudini Java) e le *mancanti* delete (č solo un codice
d'esempio), trovi che ci sia qualcosa di sbagliato nell'approccio?
Notavo una cosa: ho tirato fuori un output, con questo visitor
specifico, che č solo simile a XML, ma io volevo tirare fuori un XML
vero.  Questo perň avrebbe comportato di conoscere quando finiva la
visita di un nodo "parent" per aggiungere il tag di chiusura (mancano
i vari </frutteto>, </melo>, </arancio>, </banano>) e ho notato che
l'invocazione di accept() rimanda alle altre accept() in cascata e
quindi alle varie visit() senza un controllo specifico.
Come fare? bisogna implementare una gestione degli stati nel visitor?

 Come fare? bisogna implementare una gestione degli stati nel visitor?
Eh, l'ultimo problema e' la delicatezza della divisione dei compiti
tra Visitor e gerarchia visitata.
Ciao,
Nicola