Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 241
Steg Avatar asked Jun 12 '09 10:06

Steg


People also ask

What's the point of visitor pattern?

The purpose of a Visitor pattern is to define a new operation without introducing the modifications to an existing object structure. Imagine that we have a composite object which consists of components.

What design problem can the visitor pattern solve?

What problems can the Visitor design pattern solve? It should be possible to define a new operation for (some) classes of an object structure without changing the classes.

What is visitor pattern in C++?

Visitor in C++ Visitor is a behavioral design pattern that allows adding new behaviors to existing class hierarchy without altering any existing code.


2 Answers

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?

like image 175
Dirk Vollmar Avatar answered Oct 13 '22 12:10

Dirk Vollmar


There is the "Visitor Pattern With Default", in which you do the visitor pattern as normal but then define an abstract class that implements your IShapeVisitor class by delegating everything to an abstract method with the signature visitDefault(IShape).

Then, when you define a visitor, extend this abstract class instead of implementing the interface directly. You can override the visit* methods you know about at that time, and provide for a sensible default. However, if there really isn't any way to figure out sensible default behavior ahead of time, you should just implement the interface directly.

When you add a new IShape subclass, then, you fix the abstract class to delegate to its visitDefault method, and every visitor that specified a default behavior gets that behavior for the new IShape.

A variation on this if your IShape classes fall naturally into a hierarchy is to make the abstract class delegate through several different methods; for example, an DefaultAnimalVisitor might do:

public abstract class DefaultAnimalVisitor implements IAnimalVisitor {   // The concrete animal classes we have so far: Lion, Tiger, Bear, Snake   public void visitLion(Lion l)   { visitFeline(l); }   public void visitTiger(Tiger t) { visitFeline(t); }   public void visitBear(Bear b)   { visitMammal(b); }   public void visitSnake(Snake s) { visitDefault(s); }    // Up the class hierarchy   public void visitFeline(Feline f) { visitMammal(f); }   public void visitMammal(Mammal m) { visitDefault(m); }    public abstract void visitDefault(Animal a); } 

This lets you define visitors that specify their behavior at whatever level of specificity you wish.

Unfortunately, there is no way to avoid doing something to specify how visitors will behave with a new class - either you can set up a default ahead of time, or you can't. (See also the second panel of this cartoon )

like image 24
Daniel Martin Avatar answered Oct 13 '22 13:10

Daniel Martin