2010-02-28 53 views
6

假设我们已经有了一个类层次结构,在不修改原始类的情况下添加虚拟功能

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

,然后只需用DrawableSquare S和DrawableCircle小号取代Square一切都创作和Circle S,分别。

这是实现这个目标的最好方法,或者是有更好的东西(最好是使创建完好的Square s和Circle)。

在此先感谢。

回答

5

(我做下来还提出了一个解决方案...包涵...)

一到(几乎)解决您的问题的方法是使用一个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; 
}; 
+0

你是不是指'visitCircle(const Circle&circle)'而不是visitSquare那里? – 2010-02-28 22:12:30

+0

@飞利浦:oops ...固定。 – Dan 2010-02-28 22:27:20

+0

有趣的解决方案,我喜欢只使用部分'Visitor'模式>>模式是为了适应这种情况,而不是其他方式:) – 2010-03-01 08:25:49

3

你所描述的有点像decorator pattern。这非常适合于改变现有类的运行时行为。

但我实在不明白如何实现你的实际的例子,如果形状有没有办法可以得出,那么就没有办法在运行时要么...

改变图纸的行为,但我想这是只是一个非常简单的例子为stackoverflow?如果所有功能的基本构建块都可用,那么使用这种模式来实现确切的运行时行为当然是一个不错的选择。

+0

这不是SO的简化示例。我字面上有我想要添加绘图方法的形状。具体而言,这些形状来自Bullet Physics SDK碰撞形状,我希望能够为调试目的添加绘图功能。 – 2010-02-28 21:04:29

+0

纠正我,如果我错了,但我不认为装饰者解决这个问题。装饰者为现有的类添加一个新的行为树,而不是向现有的树添加行为。 – 2010-02-28 21:18:18

+0

它看起来像Bullet SDK有一个'btIDebugDraw'接口:http://bulletphysics.com/Bullet/BulletFull/classbtIDebugDraw.html。你从它派生出一个实现'drawBox()'和'drawSphere()'的函数的类,然后把它分配给'btCollisionWorld'或'btDynamicsWorld',然后让它画出这个世界。那会做你需要的吗? – 2010-02-28 23:30:32

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()方法可能是关键,因为它表示任何满足方法要求的类的通用行为。请注意代码膨胀,尽管编译器会为每个传递的类型实例化一个单独的方法。

这对于需要编写一个函数以处理许多没有公共基类的类型的情况很有用。我知道这不是你问的问题,但我想我会把它作为一种替代方案。

相关问题