Visitor
Separate an algorithm from the object structure it runs over, so new operations can be added without editing the element classes.
The problem
Your team maintains an app built around a large graph of geographic data. Cities, industrial zones, sightseeing areas, each node type its own class, all of it running happily in production.
Then a ticket lands: export the graph to XML. Easy, you think. Add an
exportXML() method to every node class, recurse over the graph, let
polymorphism do the rest.
The architect says no. That code is live and he is not risking a regression in the geodata classes for an export feature. He also points out, annoyingly correctly, that XML serialization has nothing to do with representing a city, and that once you ship XML somebody will want CSV, and then a report generator, and each one will mean reopening the same fragile classes.
So you try keeping the export outside and dispatching by hand.
That chain of type checks is fragile in a specific and nasty way: subclasses must be tested before their parents, and every new node type leaves every such chain in the codebase quietly one branch short.
The solution
Put the new behavior in a separate class, the visitor, and pass the element to it as an argument. The visitor gets full access to the element's data without the element knowing anything about export.
That immediately raises the dispatch problem. The visitor needs a different
method per element class, so how does the client pick the right one? Not by
overloading: the compiler resolves overloads from the static type, so a
variable declared Shape always lands in the Shape overload no matter what
it really holds.
The trick, called double dispatch, is to stop choosing and let the element
choose. Each element gets one method, accept(visitor), whose entire body
calls the visiting method matching its own class. The client calls
element.accept(v), the element calls v.visitCircle(this), and two ordinary
virtual calls have done what a page of type checks was doing badly.
Yes, this means editing the element classes after all. But you edit them once, adding one trivial method, and every behavior you invent afterwards is a brand new visitor class and zero further edits.
- 1The client loops over shapes typed as Shape. It has no idea this one is a circle, and it never finds out.
- 2Dispatch number two. The circle knows its own class, so it names the matching visiting method. No instanceof anywhere in sight.
- 3Now the visitor holds a properly typed Circle and can reach for radius, which would not exist on a plain Shape.
- 4A visitor may accumulate state as it goes, which is why a single traversal can build one coherent document.
- 5Next shape in the collection, and a composite this time.
- 6Same two-step, different landing site. The client code has not changed one character between shapes.
- 7The visitor writes the group and its child references. When the marketing team asks for JSON next month, you write a new visitor and touch zero shape classes.
Structure
Read the diagram as two hierarchies facing each other. Elements know only the
Visitor interface. Visitors know every concrete element class, because their
parameter types say so. That asymmetry is the pattern's whole cost structure.
Code
Same example three ways: XML export bolted onto a hierarchy of geometric shapes without rewriting the geometry.
When to use it
- You need to run an operation over every element of a complex structure whose nodes have different classes, especially a Composite tree.
- Auxiliary behaviors are cluttering classes that should be focused on their primary job. Export, validation, pretty-printing, and metrics are all better neighbors to each other than to your domain model.
- A behavior only makes sense for some classes in a hierarchy. Implement those visiting methods and leave the rest empty, rather than polluting the base class with a method most subclasses must stub out.
Pitfalls
- The element hierarchy must be stable. Every added or removed element class means updating the visitor interface and all its implementations. If node types churn weekly, this pattern will make you miserable.
- The forgotten override. A subclass that inherits
accept()from its parent is dispatched as its parent. It compiles, it runs, it is wrong. Every concrete element overridesaccept(), always. - Private data is out of reach. Visitors see only the public surface. Widening access to serve a visitor trades the element's encapsulation for the visitor's convenience; nesting the visitor is cleaner where the language allows it.
- Ceremony for two node types. Two element classes and one operation do not need a double-dispatch protocol. A method is fine.
Don't confuse it with
- Double dispatch itself. Double dispatch is the mechanism; Visitor is the
pattern built on it. Languages with multiple dispatch or pattern matching on
types can get Visitor's benefit without the
accept()boilerplate, which is why the pattern is rarer in Clojure or Rust than in Java. Knowing the mechanism tells you when the pattern is unnecessary. - Command. Both objectify an operation, and Visitor is reasonably called a
more powerful Command. The difference is dispatch: a command has one
execute(), while a visitor carries a family of implementations selected by the element's runtime class. - Composite. Composite builds the tree, Visitor traverses and acts on it. They are so often used together that the GoF's Composite chapter recommends Visitor for anything that is not core tree behavior.
- Iterator. Iterator solves "give me the next element" and says nothing about its type. Visitor solves "given this element's exact class, do the right thing". Combine them and you can walk any heterogeneous structure and act correctly at every node.
Check yourself
What are the two dispatches in double dispatch?