(я предлагаю решение дальше ... потерпите меня ...)
Одним из способов (почти) решения вашей проблемы является использование шаблона проектирования Visitor. Примерно так:
class DrawVisitor
{
public:
void draw(const Shape &shape); // dispatches to correct private method
private:
void visitSquare(const Square &square);
void visitCircle(const Circle &circle);
};
Тогда вместо этого:
Shape &shape = getShape(); // returns some Shape subclass
shape.draw(); // virtual method
Вы бы сделали:
DrawVisitor dv;
Shape &shape = getShape();
dv.draw(shape);
Обычно в шаблоне Visitor вы реализуете метод draw
следующим образом:
DrawVisitor::draw(const Shape &shape)
{
shape.accept(*this);
}
Но это работает только в том случае, если иерархия Shape
была разработана для посещения: каждый подкласс реализует виртуальный метод accept
, вызывая соответствующий метод visitXxxx
в Visitor. Скорее всего, он не был предназначен для этого.
Не имея возможности изменить иерархию классов для добавления виртуального метода accept
к Shape
(и всем подклассам), вам необходим какой-то другой способ отправки в правильный метод draw
. Один наивный подход заключается в следующем:
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.
}
Это сработает, но при использовании dynamic_cast наблюдается снижение производительности. Если вы можете позволить себе этот удар, это простой подход, который легко понять, отладить, поддерживать и т. Д.
Предположим, что было перечисление всех типов фигур:
enum ShapeId { SQUARE, CIRCLE, ... };
и был виртуальный метод ShapeId Shape::getId() const = 0;
, который каждый подкласс переопределил бы, чтобы вернуть ShapeId
. Тогда вы можете выполнить отправку, используя массивный оператор switch
вместо if-elsif-elsif, равного dynamic_cast
s. Или, возможно, вместо switch
используйте хеш-таблицу. В лучшем случае нужно поместить эту функцию сопоставления в одном месте, чтобы вы могли определить несколько посетителей, не повторяя каждый раз логику сопоставления.
Так что у вас, вероятно, нет и метода getid()
. Очень плохо. Как еще можно получить идентификатор, уникальный для каждого типа объекта? RTTI. Это не обязательно элегантно или надежно, но вы можете создать хеш-таблицу из type_info
указателей. Вы можете создать эту хеш-таблицу в некотором коде инициализации или построить его динамически (или оба).
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);
}
}
Там могут быть некоторые ошибки / синтаксические ошибки, я не пробовал компилировать этот пример. Я делал что-то подобное раньше - техника работает. Я не уверен, что у вас могут возникнуть проблемы с общими библиотеками.
И последнее, что я добавлю: независимо от того, как вы решите отправлять сообщения, возможно, имеет смысл создать базовый класс для посетителей:
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;
};