Search code examples
c++polymorphismclass-designsoftware-design

Drawing objects - Better class design?


I have a problem designing a class that will allow me to draw objects of various shapes.

  1. Shape is the base class
  2. Triangle, Square, Rectangle are derived classes from Shape class
  3. I have a vector<Shape*> ShapeCollection that stores the derived objects i.e. Triangle,Square, Rectangle
  4. Once I pick the an object from the vector I need to draw the object onto the screen.

At this point I am stuck at what the design of a class should be where as a single 'Drawing' class will do the drawing, consuming an object of 'Shape' class. As the vector will contain different objects of the same base class Shape. As I have a thread that picks up an object from the vector and once I have an object I must be able to draw it properly.

So more or less below is what I say

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
        }
   }
}

Where as I will have a DrawSquare, DrawTriangle function which will do the drawing.

This must be something that has been solved. There must be a better way of doing this as all this switch statement has to go away somehow!

Any guidance is much appreciated.

Thanks


@Adrian and @Jerry suggested to use virtual function, I thought of it, but I need to have my Drawing away from the base class Shape


Solution

  • You would use polymorphism.

    1. Make a pure virtual function in your base class (i.e. when declaring the function assign it to 0 as in void DrawShape() = 0;)
    2. Declare and define that function in your derived classes.

    That way you can just call DrawShape() on each of these objects even if it is passed as a Shape object.

    Alternatives (NOTE: code has not been tested):

    1. Function pointer, which is like building your own 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, which is a class that overrides operator() and also is like a delegate

      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);
      

      NOTE: 1 and 2 can also be initialised with lambda functions:

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

      NOTE: 1 could be done with a template:

      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();
      

      NOTE: 2 could be done with a template as well:

      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();
      

      Or alternatively, which would allow the passing of a stateful functor:

      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. Inherit from another class that implements the function you want either with a templated base class (IIRC, this was done in the ATL). This is just rolling your own hard-coded vtable and is called the Curiously Recurring Type Pattern (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);
            }
      };
      

      Other examples can be found here and here.

    4. Have your draw class inherit from the type of shape class which inherits from the base shape class.

      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. Use a 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);
      }
      

      NOTE: if you are using g++ 4.7, be warned unordered_map has been shown to have performance issues.