Dopo aver fatto alcune ricerche, non riesco a trovare un semplice esempio per risolvere un problema che incontro 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.
Farei la classe del 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;
};
(Immagina di avere più classi di forme: triangoli, esagoni, con ogni volta variabili dei loro proprietari e getter e setter associati. I problemi che ho affrontato avevano 8 sottoclassi, ma per l'esempio mi sono fermato a 2)
Ora ho ShapeManager
un'istanza e l'archiviazione di 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 spinbox per modificare ogni parametro per ogni tipo di forma. Ad esempio, quando seleziono un quadrato sullo schermo, il widget parametri visualizza solo i Square
parametri correlati (grazie a AbstractShape::getType()
) e propone di modificare la larghezza del quadrato. Per fare ciò ho bisogno di una funzione che mi permetta di modificare la larghezza in ShapeManager
, ed è così che lo faccio:
void ShapeManager::changeSquareWidth(int shapeIndex, float width){
Square* square = dynamic_cast<Square*>(m_shapes[shapeIndex]);
assert(square);
square->setWidth(width);
}
Esiste un design migliore che mi evita di usare dynamic_cast
e di implementare una coppia getter / setter ShapeManager
per ogni sottoclasse che posso avere? Ho già provato a utilizzare il modello ma non ci sono riuscito .
Il problema che sto affrontando non è davvero con le forme, ma con diverse Job
s per una stampante 3D (es: PrintPatternInZoneJob
, TakePhotoOfZone
, etc.) con AbstractJob
la loro classe di base. Il metodo virtuale è execute()
e non getPerimeter()
. L'unica volta che ho bisogno di usare un uso concreto è riempire le informazioni specifiche di cui un lavoro ha bisogno :
PrintPatternInZone
necessita dell'elenco dei punti da stampare, della posizione della zona, di alcuni parametri di stampa come la temperaturaTakePhotoOfZone
ha bisogno di quale zona prendere nella foto, il percorso in cui la foto verrà salvata, le dimensioni, ecc ...
Quando chiamerò execute()
, i lavori utilizzeranno le informazioni specifiche che devono realizzare per realizzare l'azione che dovrebbero fare.
L'unica volta che ho bisogno di usare il tipo concreto di un lavoro è quando riempio o visualizzo queste informazioni (se TakePhotoOfZone
Job
viene selezionato un, verrà mostrato un widget che mostra e modifica i parametri di zona, percorso e dimensioni).
Gli Job
s vengono quindi inseriti in un elenco di Job
s che accettano il primo lavoro, lo eseguono (chiamando AbstractJob::execute()
), il passaggio al successivo, avanti e indietro fino alla fine dell'elenco. (Ecco perché uso l'ereditarietà).
Per memorizzare i diversi tipi di parametri utilizzo un JsonObject
:
vantaggi: stessa struttura per qualsiasi lavoro, nessun dynamic_cast durante l'impostazione o la lettura dei parametri
problema: impossibile memorizzare i puntatori (su
Pattern
oZone
)
Credi che esista un modo migliore di archiviare i dati?
Quindi come memorizzeresti il tipo concreto diJob
per usarlo quando devo modificare i parametri specifici di quel tipo? JobManager
ha solo un elenco di AbstractJob*
.
changeValue(int shapeIndex, PropertyKey propkey, double numericalValue)
dove PropertyKey
può essere un enum o una stringa e "Width" (che indica che la chiamata al setter aggiornerà il valore di width) è uno dei valori consentiti.