(I do proponer una solución más abajo ... oso conmigo ...)
Una forma de (casi) resolver su problema es el uso de un patrón de diseño del visitante. Algo como esto:
class DrawVisitor
{
public:
void draw(const Shape &shape); // dispatches to correct private method
private:
void visitSquare(const Square &square);
void visitCircle(const Circle &circle);
};
Entonces, en lugar de esto:
Shape &shape = getShape(); // returns some Shape subclass
shape.draw(); // virtual method
puede hacer:
DrawVisitor dv;
Shape &shape = getShape();
dv.draw(shape);
Normalmente en un patrón de Visitantes que sería implementar el método draw
así:
DrawVisitor::draw(const Shape &shape)
{
shape.accept(*this);
}
Pero eso solo funciona si la jerarquía Shape
fue diseñada para ser visitada: cada subclase implementa el método virtual accept
llamando al método visitXxxx
apropiado en el Visitor. Lo más probable es que no fue diseñado para eso.
Sin ser capaz de modificar la jerarquía de clases para agregar un método virtual accept
-Shape
(y todas las subclases), necesita alguna otra manera de ser trasladados al método correcto draw
. Un enfoque ingenuo es este:
DrawVisitor::draw(const Shape &shape)
{
if (const Square *pSquare = dynamic_cast<const Square *>(&shape))
{
visitSquare(*pSquare);
}
else if (const Circle *pCircle = dynamic_cast<const Circle *>(&shape))
{
visitCircle(*pCircle);
}
// etc.
}
Eso funcionará, pero hay un golpe de rendimiento al usar dynamic_cast de esa manera. Si se lo puede permitir ese golpe, es un enfoque sencillo que es fácil de comprender, depurar, mantener, etc.
Supongamos que hubo una enumeración de todos los tipos de forma:
enum ShapeId { SQUARE, CIRCLE, ... };
y había una virtuales método ShapeId Shape::getId() const = 0;
que cada subclase anularía para devolver su ShapeId
. A continuación, puede hacer su envío utilizando una declaración masiva switch
en lugar de if-elsif-elsif de dynamic_cast
s. O quizás en lugar de switch
use una tabla hash. El mejor de los casos es poner esta función de mapeo en un lugar, para que pueda definir múltiples visitantes sin tener que repetir la lógica de mapeo cada vez.
Probablemente tampoco tenga un método getid()
. Demasiado. ¿Cuál es otra forma de obtener una identificación que sea única para cada tipo de objeto? RTTI. Esto no es necesariamente elegante o infalible, pero puede crear una tabla hash de type_info
punteros. Puedes construir esta tabla hash en algún código de inicialización o compilarla dinámicamente (o ambas cosas).
DrawVisitor::init() // static method or ctor
{
typeMap_[&typeid(Square)] = &visitSquare;
typeMap_[&typeid(Circle)] = &visitCircle;
// etc.
}
DrawVisitor::draw(const Shape &shape)
{
type_info *ti = typeid(shape);
typedef void (DrawVisitor::*VisitFun)(const Shape &shape);
VisitFun visit = 0; // or default draw method?
TypeMap::iterator iter = typeMap_.find(ti);
if (iter != typeMap_.end())
{
visit = iter->second;
}
else if (const Square *pSquare = dynamic_cast<const Square *>(&shape))
{
visit = typeMap_[ti] = &visitSquare;
}
else if (const Circle *pCircle = dynamic_cast<const Circle *>(&shape))
{
visit = typeMap_[ti] = &visitCircle;
}
// etc.
if (visit)
{
// will have to do static_cast<> inside the function
((*this).*(visit))(shape);
}
}
Podría haber algunos errores de sintaxis/errores allí, no he intentado compilar este ejemplo. He hecho algo como esto antes: la técnica funciona. Sin embargo, no estoy seguro de si podría tener problemas con las bibliotecas compartidas.
Una última cosa que voy a añadir: independientemente de lo que decida hacer el envío, es probable que tenga sentido para hacer una clase base de visitantes:
class ShapeVisitor
{
public:
void visit(const Shape &shape); // not virtual
private:
virtual void visitSquare(const Square &square) = 0;
virtual void visitCircle(const Circle &circle) = 0;
};
¿Quiere decir 'visitCircle (const Circle & circle)' en lugar de visitar Square allí? –
@Philip: uy ... arreglado. – Dan
Solución interesante, me gusta el uso de solo una parte del patrón 'Visitor'. Los patrones están destinados a adaptarse a la situación, y no al revés :) –