Después de hacer algunas investigaciones, parece que no puedo encontrar un ejemplo simple para resolver un problema que encuentro a menudo.
Digamos que quiero crear una pequeña aplicación donde pueda crear Square
s, Circle
s y otras formas, mostrarlas en una pantalla, modificar sus propiedades después de seleccionarlas y luego calcular todos sus perímetros.
Haría la clase de modelo así:
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 que tengo más clases de formas: triángulos, hexágonos, cada vez con sus variables de apuntalamiento y getters y setters asociados. Los problemas que enfrenté tenían 8 subclases, pero por el ejemplo me detuve en 2)
Ahora tengo una ShapeManager
instanciación y almacenamiento de todas las formas en una matriz:
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;
};
Finalmente, tengo una vista con spinboxes para cambiar cada parámetro para cada tipo de forma. Por ejemplo, cuando selecciono un cuadrado en la pantalla, el widget de parámetros solo muestra Square
parámetros relacionados (gracias a AbstractShape::getType()
) y propone cambiar el ancho del cuadrado. Para hacer eso, necesito una función que me permita modificar el ancho ShapeManager
, y así es como lo hago:
void ShapeManager::changeSquareWidth(int shapeIndex, float width){
Square* square = dynamic_cast<Square*>(m_shapes[shapeIndex]);
assert(square);
square->setWidth(width);
}
¿Hay un mejor diseño que me evite usar dynamic_cast
e implementar un par getter / setter ShapeManager
para cada variable de subclase que pueda tener? Ya intenté usar la plantilla pero fallé .
El problema que estoy enfrentando no es realmente con formas pero con diferentes Job
s para una impresora 3D (por ejemplo: PrintPatternInZoneJob
, TakePhotoOfZone
, etc.) con AbstractJob
como su clase base. El método virtual es execute()
y no getPerimeter()
. El único momento en que necesito usar un uso concreto es llenar la información específica que necesita un trabajo :
PrintPatternInZone
necesita la lista de puntos para imprimir, la posición de la zona, algunos parámetros de impresión como la temperaturaTakePhotoOfZone
necesita qué zona tomar en la foto, la ruta donde se guardará la foto, las dimensiones, etc.
Cuando llame execute()
, los Trabajos utilizarán la información específica que tienen para darse cuenta de la acción que se supone que deben hacer.
El único momento en que necesito usar el tipo concreto de un Trabajo es cuando completo o visualizo estas informaciones (si TakePhotoOfZone
Job
se selecciona una, se mostrará un widget que muestra y modifica los parámetros de zona, ruta y dimensiones).
Los Job
s luego se colocan en una lista de Job
s que toman el primer trabajo, lo ejecutan (llamando AbstractJob::execute()
), van al siguiente, y continúan hasta el final de la lista. (Por eso uso la herencia).
Para almacenar los diferentes tipos de parámetros utilizo a JsonObject
:
ventajas: la misma estructura para cualquier trabajo, sin Dynamic_cast al configurar o leer parámetros
problema: no puede almacenar punteros (a
Pattern
oZone
)
¿Crees que hay una mejor manera de almacenar datos?
Entonces, ¿cómo almacenarías el tipo concreto deJob
usarlo cuando tengo que modificar los parámetros específicos de ese tipo? JobManager
solo tiene una lista de AbstractJob*
.
changeValue(int shapeIndex, PropertyKey propkey, double numericalValue)
dónde PropertyKey
puede ser una enumeración o una cadena, y "Ancho" (que significa que la llamada al emisor actualizará el valor de ancho) se encuentra entre uno de los valores permitidos.