(I do proporre ulteriormente verso il basso una soluzione ... orso con me ...)
Un modo per (quasi) a risolvere il problema è quello di utilizzare un modello di progettazione visitatore. Qualcosa di simile a questo:
class DrawVisitor
{
public:
void draw(const Shape &shape); // dispatches to correct private method
private:
void visitSquare(const Square &square);
void visitCircle(const Circle &circle);
};
Poi invece di questo:
Shape &shape = getShape(); // returns some Shape subclass
shape.draw(); // virtual method
si dovrebbe fare:
DrawVisitor dv;
Shape &shape = getShape();
dv.draw(shape);
Normalmente in un modello visitatore si implementa il metodo draw
in questo modo:
DrawVisitor::draw(const Shape &shape)
{
shape.accept(*this);
}
Ma ciò funziona solo se la gerarchia Shape
è stata progettata per essere visitata: ciascuna sottoclasse implementa il metodo virtuale accept
chiamando il metodo appropriato visitXxxx
sul visitatore. Molto probabilmente non è stato progettato per questo.
Senza essere in grado di modificare la gerarchia delle classi per aggiungere un metodo virtuale accept
-Shape
(e tutte le sottoclassi), avete bisogno di qualche altro modo che vengano avviati al metodo corretto draw
. Un approccio naieve è questa:
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.
}
che funzionerà, ma v'è un calo di prestazioni a usare dynamic_cast in quel modo. Se potete permettervi quel colpo, si tratta di un approccio semplice che è facile da capire, eseguire il debug, mantenere, ecc
Supponiamo che ci fosse un'enumerazione di tutti i tipi di forme:
enum ShapeId { SQUARE, CIRCLE, ... };
e c'era un virtuale metodo ShapeId Shape::getId() const = 0;
che ciascuna sottoclasse sovrascrivere per restituire il valore ShapeId
. Quindi puoi eseguire la tua spedizione utilizzando una massiccia affermazione switch
anziché l'if-elsif-elsif di dynamic_cast
s. O forse invece di un switch
usa una tabella hash. Lo scenario migliore consiste nel mettere questa funzione di mappatura in un unico posto, in modo da poter definire più visitatori senza dover ripetere la logica di mappatura ogni volta.
Quindi probabilmente non si dispone di un metodo getid()
. Peccato. Qual è un altro modo per ottenere un ID che è unico per ogni tipo di oggetto? RTTI. Questo non è necessariamente elegante o infallibile, ma è possibile creare una tabella hash dei puntatori type_info
. Puoi costruire questo hashtable in qualche codice di inizializzazione o costruirlo dinamicamente (o entrambi).
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);
}
}
Potrebbe esserci qualche errore di bug/sintassi, non ho provato a compilare questo esempio. Ho già fatto qualcosa del genere - la tecnica funziona. Non sono sicuro che potresti incontrare problemi con le librerie condivise.
Un'ultima cosa io aggiungo: a prescindere da come si decide di fare la spedizione, probabilmente ha senso fare una classe di base visitatore:
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;
};
Intendi 'visitCircle (const Circle & circle)' piuttosto che visitareSquare lì? –
@Philip: oops ... corretto. – Dan
Soluzione interessante, mi piace usare solo parte del pattern "Visitor' pattern >> per adattarsi alla situazione, e non viceversa :) –