添加虚函数而不修改原始类

时间:2010-02-28 20:28:01

标签: c++ oop polymorphism

假设我们已经有了类的层次结构,例如

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() { ... } };

然后分别用SquareCircle替换DrawableSquareDrawableCircle s的所有作品。

这是实现这一目标的最佳方式,还是有更好的方法(最好是保留SquareCircle的完整内容。

提前致谢。

3 个答案:

答案 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()方法要求的任何类的通用行为。请注意这里的代码膨胀,因为编译器将为每个传递的类型实例化一个单独的方法。

在需要编写一个函数来处理没有公共基类的许多类型的情况下,这非常有用。我知道这不是你提出的问题,但我认为我会抛弃它作为替代方案。