Depois de fazer algumas pesquisas, não consigo encontrar um exemplo simples para resolver um problema que encontro com frequência.
Digamos que eu queira criar um pequeno aplicativo em que possa criar Square
s, se Circle
outras formas, exibi-las em uma tela, modificar suas propriedades depois de selecioná-las e depois calcular todos os seus perímetros.
Eu faria a classe model como esta:
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 eu tenho mais classes de formas: triângulos, hexágonos, com cada vez que suas variáveis de proprietários e getters e setters associados. Os problemas que enfrentei tinham 8 subclasses, mas pelo exemplo parei em 2)
Agora tenho um ShapeManager
, instanciando e armazenando todas as formas em uma 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, tenho uma visualização com caixas de rotação para alterar cada parâmetro para cada tipo de forma. Por exemplo, quando eu seleciono um quadrado na tela, o widget de parâmetro exibe apenas Square
parâmetros relacionados (graças a AbstractShape::getType()
) e propõe a alteração da largura do quadrado. Para fazer isso, preciso de uma função que permita modificar a largura ShapeManager
, e é assim que eu faço:
void ShapeManager::changeSquareWidth(int shapeIndex, float width){
Square* square = dynamic_cast<Square*>(m_shapes[shapeIndex]);
assert(square);
square->setWidth(width);
}
Existe um design melhor que me evite usar dynamic_cast
e implementar um casal getter / setter ShapeManager
para cada variável de subclasse que eu possa ter? Eu já tentei usar o modelo, mas falhei .
O problema que estou enfrentando não é realmente com formas, mas com diferentes Job
s para uma impressora 3D (ex: PrintPatternInZoneJob
, TakePhotoOfZone
, etc.) com AbstractJob
como sua classe base. O método virtual é execute()
e não getPerimeter()
. O único momento em que preciso usar o uso concreto é preencher as informações específicas de que um trabalho precisa :
PrintPatternInZone
precisa da lista de pontos para imprimir, da posição da zona, de alguns parâmetros de impressão, como a temperaturaTakePhotoOfZone
precisa de qual zona levar na foto, o caminho onde a foto será salva, as dimensões, etc ...
Quando eu ligarei execute()
, os Jobs usarão as informações específicas que eles têm para realizar a ação que devem executar.
O único momento em que preciso usar o tipo concreto de um trabalho é quando eu preencher ou exibir essas informações (se a TakePhotoOfZone
Job
for selecionada, um widget exibindo e modificando os parâmetros de zona, caminho e dimensões será mostrado).
Os Job
s são então colocados em uma lista de Job
s que recebem o primeiro trabalho, o executam (chamando AbstractJob::execute()
), passam para o próximo, até o final da lista. (É por isso que eu uso herança).
Para armazenar os diferentes tipos de parâmetros, eu uso um JsonObject
:
vantagens: mesma estrutura para qualquer trabalho, sem dynamic_cast ao definir ou ler parâmetros
problema: não é possível armazenar ponteiros (para
Pattern
ouZone
)
Você acha que existe uma maneira melhor de armazenar dados?
Então, como você armazenaria o tipo concreto doJob
para usá-lo quando eu tiver que modificar os parâmetros específicos desse tipo? JobManager
só tem uma lista de AbstractJob*
.
changeValue(int shapeIndex, PropertyKey propkey, double numericalValue)
where PropertyKey
pode ser uma enumeração ou string, e "Width" (que significa que a chamada ao setter atualizará o valor da width) está entre um dos valores permitidos.