Search code examples
oopdesign-patternsvisitor-pattern

Alternative to the visitor pattern?


I am looking for an alternative to the visitor pattern. Let me just focus on a couple of pertinent aspects of the pattern, while skipping over unimportant details. I'll use a Shape example (sorry!):

  1. You have a hierarchy of objects that implement the IShape interface
  2. You have a number of global operations that are to be performed on all objects in the hierarchy, e.g. Draw, WriteToXml etc...
  3. It is tempting to dive straight in and add a Draw() and WriteToXml() method to the IShape interface. This is not necessarily a good thing - whenever you wish to add a new operation that is to be performed on all shapes, each IShape-derived class must be changed
  4. Implementing a visitor for each operation i.e. a Draw visitor or a WirteToXml visitor encapsulates all the code for that operation in one class. Adding a new operation is then a matter of creating a new visitor class that performs the operation on all types of IShape
  5. When you need to add a new IShape-derived class, you essentially have the same problem as you did in 3 - all visitor classes must be changed to add a method to handle the new IShape-derived type

Most places where you read about the visitor pattern state that point 5 is pretty much the main criteria for the pattern to work and I totally agree. If the number of IShape-derived classes is fixed, then this can be a quite elegant approach.

So, the problem is when a new IShape-derived class is added - each visitor implementation needs to add a new method to handle that class. This is, at best, unpleasant and, at worst, not possible and shows that this pattern is not really designed to cope with such changes.

So, the question is has anybody come across alterative approaches to handling this situation?


Solution

  • You might want to have a look at the Strategy pattern. This still gives you a separation of concerns while still being able to add new functionality without having to change each class in your hierarchy.

    class AbstractShape
    {
        IXmlWriter _xmlWriter = null;
        IShapeDrawer _shapeDrawer = null;
    
        public AbstractShape(IXmlWriter xmlWriter, 
                    IShapeDrawer drawer)
        {
            _xmlWriter = xmlWriter;
            _shapeDrawer = drawer;
        }
    
        //...
        public void WriteToXml(IStream stream)
        {
            _xmlWriter.Write(this, stream);
    
        }
    
        public void Draw()
        {
            _drawer.Draw(this);
        }
    
        // any operation could easily be injected and executed 
        // on this object at run-time
        public void Execute(IGeneralStrategy generalOperation)
        {
            generalOperation.Execute(this);
        }
    }
    

    More information is in this related discussion:

    Should an object write itself out to a file, or should another object act on it to perform I/O?