10

Let's say I have a hierarchy of Item classes: Rectangle, Circle, Triangle. I want to be able to draw them, so my first possibility is add a virtual Draw() method to each:

class Item {
public:
   virtual ~Item();
   virtual void Draw() =0; 
};

However, I want to split the drawing functionality to a separate Draw library while the Core library contains only the basic representations. There's a couple of possibilities I can think of:

1 - A DrawManager that takes a list of Items and has to use dynamic_cast<> to work out what to do:

class DrawManager {
  void draw(ItemList& items) {
    FOREACH(Item* item, items) {
       if (dynamic_cast<Rectangle*>(item)) {
          drawRectangle();
       } else if (dynamic_cast<Circle*>(item)) {
          drawCircle();
       } ....
    }
  }
};

This isn't ideal as it relies on RTTI and forces one class to be aware of all items in the hierarchy.

2 - The other approach is to defer drawing responsibility to an ItemDrawer hierarchy (RectangleDrawer, etc):

class Item {
   virtual Drawer* GetDrawer() =0;
}

class Rectangle : public Item {
public:
   virtual Drawer* GetDrawer() {return new RectangleDrawer(this); }
}

This achieves the separation of concerns between the base representation of the Items and the code for drawing. The problem though is that the Item classes are dependent on the drawing classes.

How can I separate out this drawing code into a separate library? Is the solution for the Items to return a factory class of some description? However, how can this be defined so that the Core library isn't dependent on the Draw library?

4 Answers 4

3

Take a look at the visitor pattern.

Its intention is to separate an algorithm (in your case drawing) from an object structure on which it operates (items).

So a simple example for your problem would be :

class Item
{
public:
    virtual void visit(Visitable v)
    {
        v.accept(*this)
    }
};

class Rectangle : public Item
{
public:
    virtual void visit(Visitable v)
    {
        v.accept(*this)
    }
};

class Circle : public Item
{
public:
    virtual void visit(Visitable v)
    {
        v.accept(*this)
    }
};


class Visitable
{
public:
    virtual void accept(Rectangle& r);
    virtual void accept(Circle& c);
};

class Drawer : public Visitable
{
public:
    void accept(Rectangle& r)
    {
        // draw rectangle
    }   
    void accept(Circle& c)
    {
        // draw circle
    }
};


int main()
{
    Item* i1 = new Circle;
    Item* i2 = new Rectangle;

    Drawer d;
    i1->visit(d); // Draw circle
    i2->visit(d); // Draw rectangle

    return 1;
}
4
  • Interesting - I'd never thought about using a visitor with overloading to achieve polymorphism. Would this overload correctly at runtime though? Feb 1, 2013 at 14:30
  • Yes it would overload correctly. See edited answer
    – hotpotato
    Feb 1, 2013 at 14:36
  • I can see that this works. It's a shame though that each derived class has to implement the visit() method. Feb 1, 2013 at 15:21
  • This prompted me to do a bit more searching and I've now found the implementation of Loki's Visitor pattern which seems to be exactly what I'm looking for! I was familiar with the visitor pattern in terms of visiting nodes in trees, but hadn't thought of it in a more abstract manner. Feb 4, 2013 at 14:48
2

The split you describe - into two parallel hierarchies - is essentially the bridge pattern.

You worry that it makes the refined abstraction (Rectangle etc.) dependent on the implementation (RectangleDrawer), but I'm not sure how you could avoid that entirely.

You can certainly provide an abstract factory to break that dependency, but you still need some way to tell the factory which subclass to create, and that subclass still depends on the specific refined implementation. That is, even if Rectangle doesn't depend on RectangleDrawer, it still needs a way to tell the factory to build one, and RectangleDrawer itself still depends on Rectangle.

It's also worth asking whether this is a useful abstraction. Is drawing a rectangle so hard that it's worth putting in a differnt class, or are you really trying to abstract the graphics library?

2
  • I thought I had seen that pattern before of having the parallel hierarchies - thanks for jogging my mind on that. I am though wanting to abstract the graphics library. I don't want the core library to have to depend on the graphics library. Let's say that the core application manages shape objects and the library to display them is an add-on. Bear in mind though that this really isn't an application to draw rectangles -- I'm just using it as a metaphor. Feb 1, 2013 at 14:22
  • What I'm thinking is that RectangleDrawer, CircleDrawer etc. may not be the most useful way to abstract the graphics library. If, instead, you expose a set of primitives through a regular abstract interface, you still break the dependency.
    – Useless
    Feb 1, 2013 at 17:24
1

Have a look at Value Semantics and Concepts-based Polymorphism.

This work has changed how I look at polymorphism. We have used this system to great affect in repeated situations.

2
  • This looks fascinating -- seems like it's heading in the direction I'm wanting Feb 4, 2013 at 13:37
  • 1
    The link is broken. This is why answers that are essentially link-only are discouraged.
    – ajb
    Apr 9, 2016 at 4:58
0

The reason why you are having a problem is because objects should not draw themselves. Drawing something correctly depends on a billion pieces of state and a Rectangle is not going to have any of those pieces. You should have a central graphical object which represents the core state like backbuffer/depth buffer/shaders/etc, and then give it a Draw() method.

1
  • I agree that objects shouldn't draw themselves, hence the desire to move that ability into a different class to achieve a separation of concerns. Suppose these classes were Dog or Cat -- the object responsible for drawing them is a lot more complex than for drawing a rectangle. Feb 4, 2013 at 9:10

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.