Dopo aver fatto alcune ricerche, non riesco a trovare un semplice esempio per risolvere un problema che ho riscontrato spesso.
Diciamo che voglio creare una piccola applicazione in cui posso creare Square
s, Circle
s e altre forme, visualizzarle su uno schermo, modificare le loro proprietà dopo averle selezionate, e quindi calcolare tutti i loro perimetri .
Vorrei fare la classe modello in questo modo:
class AbstractShape
{
public :
typedef enum{
SQUARE = 0,
CIRCLE,
} SHAPE_TYPE;
AbstractShape(SHAPE_TYPE type):m_type(type){}
virtual ~AbstractShape();
virtual float computePerimeter() const = 0;
SHAPE_TYPE getType() const{return m_type;}
protected :
const SHAPE_TYPE m_type;
};
class Square : public AbstractShape
{
public:
Square():AbstractShape(SQUARE){}
~Square();
void setWidth(float w){m_width = w;}
float getWidth() const{return m_width;}
float computePerimeter() const{
return m_width*4;
}
private :
float m_width;
};
class Circle : public AbstractShape
{
public:
Circle():AbstractShape(CIRCLE){}
~Circle();
void setRadius(float w){m_radius = w;}
float getRadius() const{return m_radius;}
float computePerimeter() const{
return 2*M_PI*m_radius;
}
private :
float m_radius;
};
(Immaginiamo di avere più classi di forme: triangoli, esagoni, con ogni volta le loro variabili proprers e getter e setter associati.I problemi che ho affrontato hanno avuto 8 sottoclassi ma per amore dell'esempio mi sono fermato a 2)
Ora ho ShapeManager
, istanziando e memorizzando tutte le forme in un array:
class ShapeManager
{
public:
ShapeManager();
~ShapeManager();
void addShape(AbstractShape* shape){
m_shapes.push_back(shape);
}
float computeShapePerimeter(int shapeIndex){
return m_shapes[shapeIndex]->computePerimeter();
}
private :
std::vector<AbstractShape*> m_shapes;
};
Infine, ho una vista con le caselle di selezione per cambiare ogni parametro per ogni tipo di forma. Ad esempio, quando seleziono un quadrato sullo schermo, il widget dei parametri mostra solo i parametri relativi a Square
(grazie a AbstractShape::getType()
) e propone di modificare la larghezza del quadrato.
Per farlo ho bisogno di una funzione che mi permetta di modificare la larghezza in ShapeManager
, e questo è il modo in cui lo faccio:
void ShapeManager::changeSquareWidth(int shapeIndex, float width){
Square* square = dynamic_cast<Square*>(m_shapes[shapeIndex]);
assert(square);
square->setWidth(width);
}
C'è un design migliore che mi eviti di usare dynamic_cast
e di implementare una coppia getter / setter in ShapeManager
per ogni variabile sottoclasse che posso avere? Ho già provato a utilizzare modello ma non sono riuscito .
Il problema che sto affrontando non è in realtà con le forme ma con diverso Job
s per una stampante 3D (es: PrintPatternInZoneJob
, TakePhotoOfZone
, ecc.) con AbstractJob
come loro classe base. Il metodo virtuale è execute()
e non getPerimeter()
. L'unica volta che ho bisogno di utilizzare l'utilizzo concreto è di riempire le informazioni specifiche richieste da un lavoro :
-
PrintPatternInZone
richiede l'elenco di punti da stampare, la posizione della zona, alcuni parametri di stampa come la temperatura -
TakePhotoOfZone
ha bisogno di quale zona prendere in foto, il percorso in cui verrà salvata la foto, le dimensioni, ecc ...
Quando chiamerò execute()
, i lavori useranno le informazioni specifiche che hanno per realizzare l'azione che dovrebbero fare.
L'unica volta che ho bisogno di usare il tipo concreto di un lavoro è quando compilo o visualizzo queste informazioni (se è selezionato un TakePhotoOfZone
Job
, un widget che mostra e modifica la zona verranno mostrati i parametri di percorso, e dimensioni).
I Job
s vengono quindi inseriti in un elenco di Job
s che prende il primo lavoro, lo esegue (chiamando AbstractJob::execute()
), passa al successivo, e fino alla fine dell'elenco . (Questo è il motivo per cui utilizzo l'ereditarietà).
Per memorizzare i diversi tipi di parametri Uso un JsonObject
:
-
Vantaggi
-
: stessa struttura per qualsiasi lavoro, nessun dynamic_cast quando si impostano o si leggono i parametri
-
problema: impossibile memorizzare puntatori (a
Pattern
oZone
)
Credi che ci sia un modo migliore per archiviare i dati?
Quindi come vorresti memorizzare il tipo concreto di Job
per usarlo quando devo modificare i parametri specifici di quel tipo? JobManager
ha solo un elenco di AbstractJob*
.