绘图对象 - 更好的课堂设计?

时间:2013-06-22 00:10:51

标签: c++ polymorphism class-design software-design

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

  1. Shape是基类
  2. Triangle,Square,Rectangle是Shape class
  3. 的派生类
  4. 我有一个vector<Shape*> ShapeCollection来存储派生对象,即Triangle,Square, Rectangle
  5. 一旦我从矢量中选择一个对象,我需要将对象绘制到屏幕上。
  6. 在这一点上,我坚持认为类的设计应该是什么,因为单个'Drawing'类将执行绘图,消耗'Shape'类的对象。因为向量将包含相同基类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

2 个答案:

答案 0 :(得分:2)

你会使用多态。

  1. 在基类中创建一个纯虚函数(即在声明函数时将其指定为0,如void DrawShape() = 0;中所述)
  2. 在派生类中声明并定义该函数。
  3. 这样你就可以在每个对象上调用DrawShape(),即使它是作为Shape对象传递的。

    替代品(注意:代码尚未经过测试):

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

      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. 从另一个实现您想要的函数的类继承模板化基类(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);
            }
      };
      

      可以找到其他示例herehere

    4. 让您的draw类继承自type of shape类,继承自基类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
           }
      };
      
    5. 使用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已被证明有performance issues

答案 1 :(得分:1)

这是 经典演示,当您需要虚拟功能时。在基类中定义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);
    }
};

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