假设我们已经有了类的层次结构,例如
class Shape { virtual void get_area() = 0; };
class Square : Shape { ... };
class Circle : Shape { ... };
etc.
现在让我们说我希望(有效地)将virtual draw() = 0
方法添加到Shape
,并在每个子类中使用适当的定义。 然而,假设我想在不修改这些类的情况下这样做(因为它们是我不想改变的库的一部分)。
最好的方法是什么?
我是否实际上“添加”virtual
方法并不重要,我只是希望给定一个指针数组的多态行为。
我的第一个想法就是这样做:
class IDrawable { virtual void draw() = 0; };
class DrawableSquare : Square, IDrawable { void draw() { ... } };
class DrawableCircle : Circle, IDrawable { void draw() { ... } };
然后分别用Square
和Circle
替换DrawableSquare
和DrawableCircle
s的所有作品。
这是实现这一目标的最佳方式,还是有更好的方法(最好是保留Square
和Circle
的完整内容。
提前致谢。
答案 0 :(得分:5)
(我确实提出了进一步的解决方案......忍受我......)
(几乎)解决问题的一种方法是使用访问者设计模式。像这样:
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
层次结构时才有效:每个子类通过在访问者上调用相应的accept
方法来实现虚拟方法visitXxxx
。很可能它不是为此而设计的。
无法修改类层次结构以向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;
};
答案 1 :(得分:3)
您所描述的内容有点像decorator pattern。哪个非常适合更改现有类的运行时行为。
但我真的没有看到如何实现你的实际例子,如果无法绘制形状,那么就无法在运行时更改绘图行为......
但我想这只是stackoverflow的一个非常简化的例子?如果所有功能的所有基本构建块都可用,那么使用这种模式实现精确的运行时行为肯定是一个不错的选择。
答案 2 :(得分:0)
根据具体情况,您可能要考虑的一个“离墙”解决方案是使用模板为您提供编译时多态行为。在你说什么之前,我知道这不会给你传统的运行时多态性所以它可能没用,但是根据你工作环境的限制,它可以证明是有用的:
#include <iostream>
using namespace std;
// This bit's a bit like your library.
struct Square{};
struct Circle{};
struct AShape{};
// and this is your extra stuff.
template < class T >
class Drawable { public: void draw() const { cout << "General Shape" << endl; } };
template <> void Drawable< Square >::draw() const { cout << "Square!" << endl; };
template <> void Drawable< Circle >::draw() const { cout << "Circle!" << endl; };
template < class T >
void drawIt( const T& obj )
{
obj.draw();
}
int main( int argc, char* argv[] )
{
Drawable<Square> a;
Drawable<Circle> b;
Drawable<AShape> c;
a.draw(); // prints "Square!"
b.draw(); // prints "Circle!"
c.draw(); // prints "General Shape" as there's no specific specialisation for an Drawable< AShape >
drawIt(a); // prints "Square!"
drawIt(b); // prints "Circle!"
drawIt(c); // prints "General Shape" as there's no specific specialisation for an Drawable< AShape >
}
drawIt()
方法可能是关键,因为它表示满足draw()
方法要求的任何类的通用行为。请注意这里的代码膨胀,因为编译器将为每个传递的类型实例化一个单独的方法。
在需要编写一个函数来处理没有公共基类的许多类型的情况下,这非常有用。我知道这不是你提出的问题,但我认为我会抛弃它作为替代方案。