绘图对象 - 更好的类设计

Drawing objects - Better class design?

本文关键字:更好 对象 绘图      更新时间:2023-10-16

我在设计一个允许我绘制各种形状对象的类时遇到了问题。

  1. 形状是基类
  2. 三角形、正方形、矩形是从类派生Shape
  3. 我有一个存储派生对象的vector<Shape*> ShapeCollection,即 Triangle,Square, Rectangle
  4. 一旦我从矢量中选择一个对象,我需要将对象绘制到屏幕上。

在这一点上,我被困在一个类的设计应该是什么,作为一个"绘图"类将进行绘图,消耗"形状"类的对象。由于向量将包含相同基类的不同对象Shape .因为我有一个线程可以从矢量中拾取一个对象,一旦我有一个对象,我必须能够正确绘制它。

所以下面或多或少是我说的

class Drawing
{
public:
   void Draw(Shape* shape, string objectName)
   {
       // Now draw the object.
       // But I need to know which Object I am drawing or use
       // switch statements to identify somehow which object I have
       // And then draw. I know this is very BAD!!!
       // e.g.
        switch(objectName)
        {
          case "rectangle":
                DrawRectangle((Rectangle*) shape)
          break;
          //Rest of cases follow
        }
   }
}

其中,我将有一个DrawSquare,DrawTriangle函数将进行绘图。

这一定是已经解决的问题。必须有更好的方法来做到这一点,因为所有这些开关语句都必须以某种方式消失!

非常感谢任何指导。

谢谢


@Adrian和@Jerry建议使用虚函数,我想到了,但我需要让我的绘图远离基类Shape

你会使用多态性。

  1. 在你的基类中创建一个纯虚函数(即在声明函数时将其分配给 0,如 void DrawShape() = 0; (
  2. 在派生类中声明和定义该函数。

这样,即使它作为 Shape 对象传递,也可以对每个对象调用 DrawShape()

替代方案(注意:代码尚未测试(:

  1. 函数指针,就像构建自己的 vtable aka 委托。

    struct square
    {
        void (*draw)(square&);
    };
    void drawSquare(square& obj)
    {
      // draw square code
      // there is no 'this'. must access members via `obj`.
    }
    square s;
    s.draw = drawSquare;
    s.draw(s);
    
  2. Functor,这是一个覆盖 operator(( 的类,也类似于委托

    struct square
    {
        // Note that std::function can hold a function pointer as well as a functor.
        function<void(square&)> draw;
    };
    struct drawSquare
    {
        void oparator()(square& obj)
        {
            // draw square code
            // there is no 'this'. must access members via `obj`.
        }
    };
    square s;
    square s.draw = drawSquare();
    s.draw(s);
    

    注意:1 和 2 也可以使用 lambda 函数初始化:

    square s;
    s.draw = [](square& obj) {
      // draw square code
      // there is no 'this'. must access members via `obj`.
    };
    s.draw(s);
    

    注意:1 可以使用模板完成:

    struct square;
    template <void (*DRAW)(square&)>
    struct square
    {
        void draw()
        {
            DRAW(*this);
        }
    };
    void drawSquare(square& obj)
    {
      // draw square code
      // there is no 'this'. must access members via `obj`.
    }
    square s<&drawSquare>;
    s.draw();
    

    注意:2也可以使用模板完成:

    template <typename DRAW>
    struct square
    {
        void draw()
        {
            // First set of parentheses instantiate the DRAW object.
            // The second calls the functor.
            DRAW()(*this);
        }
    };
    struct drawSquare
    {
        void oparator()(square& obj)
        {
            // draw square code
            // there is no 'this'. must access members via `obj`.
        }
    };
    square s<drawSquare>;
    s.draw();
    

    或者,这将允许传递有状态函子:

    template <typename DRAW>
    struct square
    {
        DRAW draw;
    };
    struct drawSquare
    {
        void operator()(square& obj)
        {
            // draw square code
            // there is no 'this'. must access members via `obj`.
        }
    };
    square s<drawSquare>;
    s.draw = drawSquare();
    s.draw(s);
    
  3. 从另一个类继承,该类使用
  4. 模板化基类实现所需的函数(IIRC,这是在 ATL 中完成的(。这只是滚动你自己的硬编码vtable,称为奇怪的重复类型模式(CRTP(。

    template <class D>
    struct shape
    {
       inline void draw() { return static_cast<D&>(*this).draw(); }
    };
    void draw(square& obj)
    {
        // draw square code
        // No 'this' available. must access shape members via `obj`.
    }
    struct square : public D<square>
    {
          void draw()
          {
              drawSquare(*this);
          }
    };
    

    其他示例可以在这里和这里找到。

  5. draw类继承自从基类继承shape type of shape类。

    struct shape
    {
         virtual void draw() = 0;
    };
    struct square : public shape
    {
    };
    struct drawSquare : public square
    {
         virtual void draw()
         {
             // draw square code
             // you access the square's public or protected members from here
         }
    };
    
  6. 使用std::unordered_map

    #include <unordered_map>
    #include <typeinfo>
    #include <functional>
    struct shape { };
    struct square : public shape { };
    void drawSquare(shape& o)
    {
         // this will throw an exception if dynamic cast fails, but should
         // never fail if called from function void draw(shape& obj).
         square& obj = dynamic_cast<square&>(o);
         // draw square code
         // must access shape members via `obj`.
    }
    std::unordered_map<size_t, std::function<void(shape&)>> draw_map
    {
        { type_id(square).hash(), drawSquare }
    };
    void draw(shape& obj)
    {
         // This requires the RTTI (Run-time type information) to be available.
         auto it = draw_map.find(type_id(obj).hash());
         if (it == draw_map.end())
             throw std::exception(); // throw some exception
         (*it)(obj);
    }
    

    注意:如果您使用的是 g++ 4.7,请注意unordered_map已被证明存在性能问题。

这几乎是你想要一个虚拟函数的经典演示。在基类中定义一个draw,然后在每个派生类中重写它。然后,若要绘制所有对象,请单步执行集合并为每个对象调用draw()成员。

class shape { 
// ...
    virtual void draw(canvas &c) = 0;
};
class square : public shape {
    int x, y, size;
// ...
    virtual void draw(canvas &c) { 
         c.move_to(x, y);
         c.draw_to(x+size, y);
         c.draw_to(x+size, y+size);
         c.draw_to(x, y+size);
         c.draw_to(x, y);
    }
};

。等等,对于您关心的每种形状。

编辑:使用策略类,你最终会得到沿着这条线模糊的代码:

template <class draw>
class shape {
// ...
    virtual void draw(canvas &c) = 0;
};
template <class d>
class square : public shape<d> { 
    // ...
    virtual void draw(canvas &c) { 
        d.square(x, y, size, c);
    }
};

另一种可能性是使用访客模式。当您需要/想要遍历更复杂的结构而不是简单的线性序列时,通常会使用此方法,但也可以在此处使用。这已经足够复杂了,在这里可能有点多,但如果你搜索"访客模式",你应该找到相当数量的材料。