Pet*_*der 6 c++ oop polymorphism
假设我们已经有了类的层次结构,例如
class Shape { virtual void get_area() = 0; };
class Square : Shape { ... };
class Circle : Shape { ... };
etc.
Run Code Online (Sandbox Code Playgroud)
现在让我们说我想(有效地)在每个子类中添加一个适当定义的virtual draw() = 0方法Shape.但是,假设我想在不修改这些类的情况下这样做(因为它们是我不想改变的库的一部分).
最好的方法是什么?
我是否真的"添加"一个virtual方法并不重要,我只是想要一个指针数组的多态行为.
我的第一个想法是这样做:
class IDrawable { virtual void draw() = 0; };
class DrawableSquare : Square, IDrawable { void draw() { ... } };
class DrawableCircle : Circle, IDrawable { void draw() { ... } };
Run Code Online (Sandbox Code Playgroud)
然后分别用s和s 替换Squares和Circles的所有创建.DrawableSquareDrawableCircle
这是实现这一目标的最佳方式,还是有更好的东西(最好是保留Squares和Circles完整的东西).
提前致谢.
(我确实提出了进一步的解决方案......忍受我......)
(几乎)解决问题的一种方法是使用访问者设计模式.像这样的东西:
class DrawVisitor
{
public:
void draw(const Shape &shape); // dispatches to correct private method
private:
void visitSquare(const Square &square);
void visitCircle(const Circle &circle);
};
Run Code Online (Sandbox Code Playgroud)
而不是这个:
Shape &shape = getShape(); // returns some Shape subclass
shape.draw(); // virtual method
Run Code Online (Sandbox Code Playgroud)
你会这样做:
DrawVisitor dv;
Shape &shape = getShape();
dv.draw(shape);
Run Code Online (Sandbox Code Playgroud)
通常在访问者模式中,您将实现如下draw方法:
DrawVisitor::draw(const Shape &shape)
{
shape.accept(*this);
}
Run Code Online (Sandbox Code Playgroud)
但是只有在Shape层次结构被设计为被访问时才有效:每个子类accept通过调用visitXxxxVisitor上的相应方法来实现虚方法.很可能它不是为此而设计的.
如果无法修改类层次结构以accept向Shape(以及所有子类)添加虚拟方法,则需要一些其他方法来分派到正确的draw方法.一个naieve方法是这样的:
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.
}
Run Code Online (Sandbox Code Playgroud)
这样可行,但以这种方式使用dynamic_cast会受到性能影响.如果你能负担得起,那么它是一种易于理解,调试,维护等的简单方法.
假设存在所有形状类型的枚举:
enum ShapeId { SQUARE, CIRCLE, ... };
Run Code Online (Sandbox Code Playgroud)
并且有一个虚方法ShapeId Shape::getId() const = 0;,每个子类都会覆盖它以返回它ShapeId.那么你可以使用一个庞大的switch声明而不是dynamic_casts 的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);
}
}
Run Code Online (Sandbox Code Playgroud)
可能是那里的一些错误/语法错误,我还没有尝试编译这个例子.我之前做过类似的事情 - 技术有效.我不确定您是否可能遇到共享库的问题.
我要补充的最后一件事是:无论你如何决定进行调度,建立一个访客基类可能是有意义的:
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;
};
Run Code Online (Sandbox Code Playgroud)