Skip to main content

Visitor

complexitypopularity

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.

Application (client)Circle (element)CompoundShape (element)XMLExportVisitoraccept(exportVisitor)1visitCircle(this)2reads id, center, radius3buffer.append(circle xml)4accept(exportVisitor)5visitCompoundShape(this)6reads id and children ids7
  1. 1The client loops over shapes typed as Shape. It has no idea this one is a circle, and it never finds out.
  2. 2Dispatch number two. The circle knows its own class, so it names the matching visiting method. No instanceof anywhere in sight.
  3. 3Now the visitor holds a properly typed Circle and can reach for radius, which would not exist on a plain Shape.
  4. 4A visitor may accumulate state as it goes, which is why a single traversal can build one coherent document.
  5. 5Next shape in the collection, and a composite this time.
  6. 6Same two-step, different landing site. The client code has not changed one character between shapes.
  7. 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.

«interface»Shapemove(x, y)draw()accept(v: Visitor)ELEMENT«interface»VisitorvisitDot(d)visitCircle(c)visitRectangle(r)visitCompoundShape(cs)VISITORDotaccept(v) { v.visitDot(this) }CONCRETECircleaccept(v) { v.visitCircle(this) }CONCRETECompoundShapechildren: Shape[]accept(v) { v.visitCompoundShape(this) }CONCRETEXMLExportVisitorvisitDot(d)visitCircle(c)visitRectangle(r)visitCompoundShape(cs)CONCRETE
implementsextendsuses

Code

Same example three ways: XML export bolted onto a hierarchy of geometric shapes without rewriting the geometry.

// Option one: bolt the export onto every shape class.
class Dot implements Shape is
method draw() is
// ...
method exportXML() is // production code, now being edited
// ...
 
class Circle extends Dot is
method exportXML() is // and here
// ...
// ...and in Rectangle, and CompoundShape, and every future shape. Export
// logic has nothing to do with geometry, and the architect will not sign
// off on touching classes that already work.
 
// Option two: keep it outside, and check types by hand.
foreach (shape in allShapes)
if (shape instanceof Circle)
exportCircle((Circle) shape)
else if (shape instanceof Rectangle)
exportRectangle((Rectangle) shape)
else if (shape instanceof Dot)
exportDot((Dot) shape)
// Order matters here, because Circle extends Dot. Get it wrong and
// circles export as dots. Add a shape and every such chain in the
// codebase is silently one branch out of date.
// One method added to the hierarchy, once and never again:
class Circle extends Dot is
method accept(v: Visitor) is
v.visitCircle(this)
 
// Every future behavior is a new class, not an edit:
class XMLExportVisitor implements Visitor is
method visitCircle(c: Circle) is
// id, centre, radius
 
class AreaCalculatorVisitor implements Visitor is
method visitCircle(c: Circle) is
// pi * r * r
 
// And the client never mentions a concrete shape:
foreach (shape in allShapes) do
shape.accept(visitor)
// The element interface gains exactly one new method, once.
interface Shape is
method move(x, y)
method draw()
method accept(v: Visitor)
 
// Each concrete element implements accept() by calling the visiting method
// that matches its own class. That is the whole contribution of the element.
class Dot implements Shape is
// ...
method accept(v: Visitor) is
v.visitDot(this)
 
class Circle extends Dot is
// ...
// It inherits from Dot but must still override accept(), or every
// circle in the system quietly exports itself as a dot.
method accept(v: Visitor) is
v.visitCircle(this)
 
class Rectangle implements Shape is
// ...
method accept(v: Visitor) is
v.visitRectangle(this)
 
class CompoundShape implements Shape is
// ...
method accept(v: Visitor) is
v.visitCompoundShape(this)
 
// One visiting method per concrete element class. The parameter type is
// what lets the visitor reach for class-specific data.
interface Visitor is
method visitDot(d: Dot)
method visitCircle(c: Circle)
method visitRectangle(r: Rectangle)
method visitCompoundShape(cs: CompoundShape)
 
// A concrete visitor is one behavior, spelled out for every element class.
// It may also accumulate state across the traversal.
class XMLExportVisitor implements Visitor is
private field buffer
 
method visitDot(d: Dot) is
// Write the dot's id and centre coordinates.
 
method visitCircle(c: Circle) is
// Write the circle's id, centre coordinates and radius.
 
method visitRectangle(r: Rectangle) is
// Write the rectangle's id, top-left corner, width and height.
 
method visitCompoundShape(cs: CompoundShape) is
// Write the group's id and the ids of its children.
 
// The client runs an operation over the whole structure without ever
// naming a concrete element class.
class Application is
field allShapes: array of Shape
 
method export() is
exportVisitor = new XMLExportVisitor()
foreach (shape in allShapes) do
shape.accept(exportVisitor)

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 overrides accept(), 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

Question 1 of 5

What are the two dispatches in double dispatch?