After doing some researches I can not seem to find a simple example resolving a problem I encounter often.
Let's say I want to create a little application where I can create Square
s, Circle
s, and other shapes, display them on a screen, modify their properties after selecting them, and then compute all of their perimeters.
I would do the model class like this:
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;
};
(Imagine I have more classes of shapes: triangles, hexagones, with each time their proprers variables and associated getters and setters. The problems I faced had 8 subclasses but for the sake of the example I stopped at 2)
I now have a ShapeManager
, instantiating and storing all the shapes in an 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;
};
Finally, I have a view with spinboxes to change each parameter for each type of shape. For example, when I select a square on the screen, the parameter widget only displays Square
-related parameters (thanks to AbstractShape::getType()
) and proposes to change the width of the square.
To do that I need a function allowing me to modify the width in ShapeManager
, and this is how I do it:
void ShapeManager::changeSquareWidth(int shapeIndex, float width){
Square* square = dynamic_cast<Square*>(m_shapes[shapeIndex]);
assert(square);
square->setWidth(width);
}
Is there a better design avoiding me to use the dynamic_cast
and to implement a getter/setter couple in ShapeManager
for each subclass variables I may have? I already tried to use template but failed.
The problem I'm facing is not really with Shapes but with different Job
s for a 3D printer (ex: PrintPatternInZoneJob
, TakePhotoOfZone
, etc.) with AbstractJob
as their base class. The virtual method is execute()
and not getPerimeter()
. The only time I need to use concrete usage is to fill the specific information a job needs :
PrintPatternInZone
needs the list of points to print, the position of the zone, some printing parameters like the temperatureTakePhotoOfZone
needs what zone to take into photo, the path where the photo will be saved, the dimensions, etc...
When I will then call execute()
, the Jobs will use the specific information they have to realise the action they are supposed to do.
The only time I need to use the concrete type of a Job is when I fill or display theses informations (if a TakePhotoOfZone
Job
is selected, a widget displaying and modifying the zone, path, and dimensions parameters will be shown).
The Job
s are then put into a list of Job
s which take the first job, executes it (by calling AbstractJob::execute()
), the goes to the next, on and on until the end of the list. (This is why I use inheritance).
To store the different types of parameters I use a JsonObject
:
advantages : same structure for any job, no dynamic_cast when setting or reading parameters
problem : can't store pointers (to
Pattern
orZone
)
Do you thing there is a better way of storing data?
Then how would you store the concrete type of the Job
to use it when I have to modify the specific parameters of that type? JobManager
only has a list of AbstractJob*
.
changeValue(int shapeIndex, PropertyKey propkey, double numericalValue)
wherePropertyKey
can be an enum or a string, and "Width" (which signifies that the call to the setter will update the value of width) is among one of the allowed values.