One way to look at it is that the visitor pattern is a way of letting your clients add additional methods to all of your classes in a particular class hierarchy. It is useful when you have a fairly stable class hierarchy, but you have changing requirements of what needs to be done with that hierarchy.
The visitor pattern is used when: Similar operations have to be performed on objects of different types grouped in a structure (a collection or a more complex structure). There are many distinct and unrelated operations needed to be performed.
The Visitor design pattern is one of the twenty-three well-known Gang of Four design patterns that describe how to solve recurring design problems to design flexible and reusable object-oriented software, that is, objects that are easier to implement, change, test, and reuse.
In “double dispatch”, the operation executed depends on: the name of the request, and the type of TWO receivers (the type of the Visitor and the type of the element it visits). This essentially means different visitors can visit the same type and different types can be visited by the same visitor.
The issue arises when you have a complex structure, i.e., a hierarchy or something else that's not simply linear. When you can't simply iterate over the structure, a visitor is very handy.
If I have a hierarchy (or tree), each Node has a list of children. When I want to apply a process to every node in the tree, it's pleasant to create a Visitor.
A Node can then apply the Visitor to itself, and each of its child Nodes. Each child, transitively, does the same (apply the Visitor to itself and then any children).
This use of a Visitor works out very nicely.
When you have a super-simple data structure, Visitor doesn't add a lot of value.
Visitor pattern is a hack for languages that don't support multiple dispatch directly (language like C++ and Java only support single dispatch based on object. Multiple dispatch is supported by CLOS). In this case, if you want both Bill and Customer to be polymorphic, you'll have to use two interface, BillVisitor and Customer in single dispatch languages. For example: the implementation of accept is typically:
void accept(BillVisitor visitor) { visitor.bill(this); } // java syntax
Notice here, both Customer#accept and BillVisitor#bill can be overridden by their respective subclasses, resulting very rich combination of run-time behavior that cannot be achieved otherwise. It's really a superset of what most other people described here as a substitute of closure to apply functionality to complex data structures.
Another nice perk of visitors, is they are easy to extend, and if your language allows it, you can even use lamdbas to clean things up.
In my CAD/CAM application I have Paths and Collections of Paths (PathList). Sometimes I have to generate a collection of shapes. Not only I have to generate this particular collection of shapes I have to include it with another collection of shapes. I often need a dozen or more parameters to convey all the information needed to do the calculations.
All shape calculations (simple or complex) in my CAM are funneled a PathList that is sent out to the different machines.
With this design it would best to have some setup that involves adding the result to a single collection.
The Path Visitor fits this nicely. Each shape calculation is encapsulated into it's own class with the properties as complex as the needed.
So the Kitchen Hood Visitor may had 8 shapes to the Pathlist
While the Kitchen Counter Visitor adds 6 shapes.
The drawer Visitor adds a few more.
Then I pass out the resulting PathList to the rest of the system like any other shape generator.
I easily have options by adding another visitor or not running some. For a guy who only wants a counter and drawers, I only need to run two visitors.
The resulting code is very readable which is important when I have revisit this area 3, 5, or 10 years down the line. In addition because of encapsulation changes to one visitor have minimal if any impact on other visitors.
In addition I now have a standardized design pattern to add any new functionality to a PathList by implementing the visitor pattern.
For example it used to be that our property editor only worked on a single path. When we changed to editing multiple paths it was easy to implement custom visitors to do global changes to all paths. It was more readable then using for loops inside of delegates.
But ultimately it comes down to judgment and preference.
In both case, the visitor is separated from the customer class. The advantage would be if you wanted to abstract the visitor from the caller class as well. In the second case the calling class has to know about billing. You could instead have another routine somewhere that would return an IVisitor. The calling code could then call Custom.Accept(IVisitor) and not know anything about what the visitor is doing.
Basically in this case and the case the S.Lott mentioned, you can think of the visitor like a delegate. It's a function that you can pass around like an object and use where needed.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With