C ++如何替换这个if ... else语句?

时间:2010-01-28 06:08:07

标签: c++ polymorphism

我有以下C ++代码(简化版):

class Shape
{
    bool isCircle = false;
    bool isSquare = false;
}

class Circle : public Shape
{
    // some special members/methods
}

class Square : public Shape
{
    // some special members/methods
}

class CAD
{
    virtual DrawCircle(Circle * circle) = 0;
}

class SWX : public CAD
{
    virtual DrawCircle(Circle * circle){// do some stuff that draws circle on SWX system}
}

class PRO : public CAD
{
    virtual DrawCircle(Circle * circle){// do some stuff that draws circle on PRO system}
}

int main()
{
    Circle * circle = new Circle();
    circle->isCircle = true;

    Square * sq = new Square;
    sq->isSquare = true;

    vector<Shape*> shapes;
    shapes.push_back(circle);
    shapes.push_back(sq);

    SWX * swx = new SWX();

    for( int i = 0 ; i < shapes.size() ; ++i )
    {
        if( shapes[i]->isCircle )
    {
        SWX->DrawCircle((Circle*)(shapes[i]));
    }
    else if( shapes[i]->isSquare )
    {
        SWX->DrawSquare((Square*)(shapes[i]));
    }
}

我希望不再需要if ... else(如果可能的话,在下面所述的限制范围内)。

我现在的约束是:

  • CAD和派生类是具有各种外部依赖关系的巨大类。
  • CAD类不能与Shape和派生类合并(这本来是理想的,因为我可以使用多态来解决我的问题),因为其他项目/类依赖于Shape类而不能依赖于CAD类。
  • 有十几个Shape派生类和六个CAD派生类,如果......其他地方正在多个地方发生 - 所以如果任何解决方案都很容易理解(更容易说服我队友改变遗留代码)。

欢迎您提出任何建议/意见/解决方案。

9 个答案:

答案 0 :(得分:8)

此问题的标准解决方案,特别是考虑到您对依赖关系的限制,是使用Visitor Pattern

以下是访客模式在您的情况下如何运作:

  • 您需要一个抽象的ShapeVisitor课程。它为Shape的每个具体子类都有一个抽象的Visit方法。例如:Visit(Circle*)Visit(Square*)
  • Shape有一个抽象的AcceptVisitor(ShapeVisitor*)方法。
  • 每个Shape子类实现AcceptVisitor只需调用visitor->Visit(this)
  • 每个CAD班级都是({a-a,由你决定)a ShapeVisitorVisit方法为特定类型的Shape执行适当的绘制。无需条件或铸造。

以下是您的代码的修改版本,它以极低影响的方式使用访问者模式:

class Circle;
class Square;
class ShapeVisitor
{
    virtual void Visit(Circle *circle) = 0;
    virtual void Visit(Square *square) = 0;
}

class Shape
{
    virtual void AcceptVisitor(ShapeVisitor *visitor) = 0;
}


class Circle : public Shape
{
    // some special members/methods

    virtual void AcceptVisitor(ShapeVisitor *visitor)
    {
        visitor->Visit(this);
    }
}

class Square : public Shape
{
    // some special members/methods

    virtual void AcceptVisitor(ShapeVisitor *visitor)
    {
        visitor->Visit(this);
    }
}

class CAD : public ShapeVisitor
{
    virtual DrawCircle(Circle *circle) = 0;
    virtual DrawSquare(Square *square) = 0;

    virtual void Visit(Circle *circle) {
        DrawCircle(circle);
    }

    virtual void Visit(Square *square) {
        DrawSquare(square);
    }
}

class SWX : public CAD
{
    virtual DrawCircle(Circle *circle){// do some stuff that draws circle on SWX system}

}

class PRO : public CAD
{
    virtual DrawCircle(Circle * circle){// do some stuff that draws circle on PRO system}
}

int main()
{
    Circle * circle = new Circle();
    Square * sq = new Square;

    vector<Shape*> shapes;
    shapes.push_back(circle);
    shapes.push_back(sq);

    SWX * swx = new SWX();

    for( int i = 0 ; i < shapes.size() ; ++i )
    {
        shapes[i]->AcceptVisitor(SWX);
    }
}

在这段代码中,我选择让CAD实际上是ShapeVisitor的子类。此外,由于您已经在CAD中使用了虚拟方法来绘制,我在那里实现了Visit方法(一次),而不是在每个子类中实现一次。一旦您将客户端切换到使用AcceptVisitor而不是直接调用Draw *方法,您可以使这些方法受到保护,然后最终将Visit方法的实现向下移动到子类(即:重构以删除Visit(Foo*)调用DrawFoo(Foo*))导致的额外间接级别。

答案 1 :(得分:6)

这是DoubleDispatch的经典案例,您需要为每个可能的(形状,CAD)对分别设置一个方法:

  • 点击isSquare / isCircle字段。
  • virtual void DrawOn(CAD*)添加到Shape界面。
  • 实施Circle::DrawOn(CAD*)(例如):

    void Circle::DrawOn(CAD *c) {
      c->DrawCircle(this);
    }
    

    这是一个“技巧”,它允许像myCircle->DrawOn(mySWX)这样的调用调用正确的方法,无论Shape或CAD的类型如何。

答案 2 :(得分:1)

你有一些相当不稳定的OO,但至少DrawXxxx应该成为Draw()。然后,Circle,Square和其他形状将定义Draw()方法,该方法为Shape上的虚拟Draw方法提供实现。然后你可以在任何形状上调用Draw,它会做正确的事情。

isXxxx布尔也应该去。类知道它们是什么,instanceof可以告诉你(虽然没有必要检查你何时绘制,因为这将是一个虚方法调用)。

答案 3 :(得分:1)

为什么不只是SWX-&gt; Draw(shapes [i]);?您需要添加两个Draw方法,其中一个采用Circle,另一个采用方形?

答案 4 :(得分:1)

这是非常基本的OO多态性。圆形和方形是形状的专用版本。他们每个人都应该知道处理你的cad课程需要什么样的专业行为。

class Shape
{
    virtual void DrawWithCAD(CAD * cad) = 0;
}

class Circle : public Shape
{
    virtual void DrawWithCAD(CAD * cad)
    {
        cad->DrawCircle(this);
    }
}

class Square : public Shape
{
    virtual void DrawWithCAD(CAD * cad)
    {
        cad->DrawSquare(this);
    }
}

然后你的main()循环将变为:

for( int i = 0 ; i < shapes.size() ; ++i )
{
    shapes[i]->DrawWithCAD(swx);
}

答案 5 :(得分:1)

为什么不定义一个简单的ICAD接口?由于CAD取决于Shape,因此不会增加复杂性:

class Shape
{
    Draw(ICAD* cad) = 0;        
}

class Circle : public Shape
{
    Draw(ICAD* cad)
    {
        ICAD->DrawCircle(self)
     }
}

class Square : public Shape
{
    Draw(ICAD* cad)
    {
        ICAD->DrawSquare(self)
     }
}

DrawSquare(self)看起来很有趣,但我不知道CAD类对形状对象的作用。

class ICAD
{
    virtual DrawSquare(Square* square) = 0;
    virtual DrawCircle(Circle * circle) = 0;
}

我认为CAD类比那些抽象方法更多,这就是为什么你不把它与Shape结合起来。

class CAD : public ICAD
{
    // big CAD class...
}

class SWX : public CAD
{
    virtual DrawCircle(Circle * circle){// do some stuff that draws circle on SWX system}
}

class PRO : public CAD
{
    virtual DrawCircle(Circle * circle){// do some stuff that draws circle on PRO system}
}

int main()
{
    Circle * circle = new Circle();
    Square * sq = new Square;

    vector<Shape*> shapes;
    shapes.push_back(circle);
    shapes.push_back(sq);

    SWX * swx = new SWX();

    for( int i = 0 ; i < shapes.size() ; ++i )
    {
        shapes[i]->Draw(swx);
    }
}

答案 6 :(得分:0)

这可能不是理想的解决方案,但您可以提供CAD :: Draw成员函数并重载它以处理每种不同类型的形状。类似的东西:

class CAD {
  public:
    virtual void Draw(const Circle& circle) = 0;
    virtual void Draw(const Square& square) = 0;
};

然后,你可以在任何支持的对象上调用Draw,而不需要任何if语句。

答案 7 :(得分:0)

我要解决这个问题的方法是让各个形状类将自己绘制到某种绘图“canvas”(一个提供绘图缓冲区和一些基本绘图函数的类,例如直线,矩形和椭圆)。 / p>

然后SWX类会调用Shape的{​​{1}}方法,为其提供自己的画布:

Draw

答案 8 :(得分:0)

为每个Shape重写多个全局Draw函数...(DrawCircle和朋友是虚拟的,所以我们可以使用多态来调用CAD对象)

void Draw(CAD *cad, Circle *circle){
    cad->DrawCircle(circle);
}

void Draw(CAD *cad, Square *square){
    cad->DrawSquare(square);
}