Après avoir fait quelques recherches, je n'arrive pas à trouver un exemple simple pour résoudre un problème que je rencontre souvent.
Disons que je veux créer une petite application où je peux créer des Square
s, Circle
s et d'autres formes, les afficher sur un écran, modifier leurs propriétés après les avoir sélectionnées, puis calculer tous leurs périmètres.
Je ferais la classe modèle comme ceci:
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;
};
(Imaginez que j'ai plus de classes de formes: triangles, hexagones, avec à chaque fois leurs variables proprers et getters et setters associés. Les problèmes que j'ai rencontrés avaient 8 sous-classes, mais pour l'exemple, je me suis arrêté à 2)
J'ai maintenant un ShapeManager
, instanciant et stockant toutes les formes dans un tableau:
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;
};
Enfin, j'ai une vue avec des spinbox pour changer chaque paramètre pour chaque type de forme. Par exemple, lorsque je sélectionne un carré à l'écran, le widget de paramètres affiche uniquement les Square
paramètres liés (grâce à AbstractShape::getType()
) et propose de changer la largeur du carré. Pour ce faire, j'ai besoin d'une fonction me permettant de modifier la largeur en ShapeManager
, et voici comment je le fais:
void ShapeManager::changeSquareWidth(int shapeIndex, float width){
Square* square = dynamic_cast<Square*>(m_shapes[shapeIndex]);
assert(square);
square->setWidth(width);
}
Existe-t-il une meilleure conception m'évitant d'utiliser le dynamic_cast
et d'implémenter un couple getter / setter ShapeManager
pour chaque variable de sous-classe que je pourrais avoir? J'ai déjà essayé d'utiliser le modèle mais j'ai échoué .
Le problème que je suis confronté est pas vraiment avec des formes mais avec différentes Job
s pour une imprimante 3D (ex: PrintPatternInZoneJob
, TakePhotoOfZone
, etc.) avec AbstractJob
comme leur classe de base. La méthode virtuelle est execute()
et non getPerimeter()
. La seule fois où j'ai besoin d'utiliser concrètement est de remplir les informations spécifiques dont un travail a besoin :
PrintPatternInZone
a besoin de la liste des points à imprimer, de la position de la zone, de certains paramètres d'impression comme la températureTakePhotoOfZone
a besoin de quelle zone à prendre en photo, le chemin où la photo sera enregistrée, les dimensions, etc ...
Lorsque j'appellerai ensuite execute()
, les Jobs utiliseront les informations spécifiques dont ils disposent pour réaliser l'action qu'ils sont censés faire.
La seule fois où j'ai besoin d'utiliser le type concret d'un Job est quand je remplis ou affiche ces informations (si a TakePhotoOfZone
Job
est sélectionné, un widget affichant et modifiant les paramètres de zone, de chemin et de dimensions sera affiché).
Les Job
s sont ensuite mis dans une liste de Job
s qui prennent le premier job, l'exécute (en appelant AbstractJob::execute()
), le passe au suivant, et ainsi de suite jusqu'à la fin de la liste. (C'est pourquoi j'utilise l'héritage).
Pour stocker les différents types de paramètres, j'utilise JsonObject
:
avantages: même structure pour n'importe quel travail, pas de dynamic_cast lors du réglage ou de la lecture des paramètres
problème: impossible de stocker des pointeurs (vers
Pattern
ouZone
)
Pensez-vous qu'il existe une meilleure façon de stocker les données?
Alors, comment stockeriez-vous le type concret duJob
pour l'utiliser lorsque je dois modifier les paramètres spécifiques de ce type? JobManager
n'a qu'une liste de AbstractJob*
.
changeValue(int shapeIndex, PropertyKey propkey, double numericalValue)
où PropertyKey
peut être une énumération ou une chaîne, et "Largeur" (ce qui signifie que l'appel au setter mettra à jour la valeur de largeur) est parmi l'une des valeurs autorisées.