(我做下来还提出了一个解决方案...包涵...)
一到(几乎)解决您的问题的方法是使用一个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);
通常在一个访问者模式,你将实现draw
方法是这样的:
DrawVisitor::draw(const Shape &shape)
{
shape.accept(*this);
}
但是,只有在设计访问Shape
层次结构时才有效:每个子类通过在访问者上调用适当的visitXxxx
方法来实现虚拟方法accept
。它最有可能不是为此而设计的。
如果不能修改类层次结构以将虚拟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
声明来代替dynamic_cast
的if-elsif-elsif。或者,也许而不是switch
使用哈希表。最好的情况是把这个映射函数放在一个地方,这样你就可以定义多个访问者,而不必每次都重复映射逻辑。
所以你可能没有getid()
方法。太糟糕了。什么是获得每种类型对象唯一的ID的另一种方式? 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;
};
你是不是指'visitCircle(const Circle&circle)'而不是visitSquare那里? – 2010-02-28 22:12:30
@飞利浦:oops ...固定。 – Dan 2010-02-28 22:27:20
有趣的解决方案,我喜欢只使用部分'Visitor'模式>>模式是为了适应这种情况,而不是其他方式:) – 2010-03-01 08:25:49