Visitor Pattern
Put each operation over a fixed family of classes into its own visitor object, so new operations do not touch the classes.
Overview
The problem: many operations over a fixed set of types
An expression tree has a few node types: numbers, additions, multiplications. The program needs many operations on that tree: evaluate it, print it, count its nodes, check its types, simplify it. The obvious design adds one method per operation to every node class.
- Num — method — method — method — method — method
- Add — method — method — method — method — method
- Mul — method — method — method — method — method
Each new operation is a new column: one more method in every node class, and code for one concern (say, printing) scattered across three files. When the set of node types is stable and the set of operations keeps growing, that is the wrong way around. The Visitor pattern turns each column into one class.
The pattern
Visitor is one of the 23 patterns in the 1994 Gang of Four book (Gamma, Helm, Johnson and Vlissides). Each element class gets a single method, accept(visitor), whose body is always the same one line: call the visitor's method for my type, passing myself. Each visitor class has one method per element type and holds the whole of one operation.
Double dispatch
Ordinary method calls pick the code to run from one type: the receiver's. Here the code depends on two types, the node and the visitor. The pattern gets there with two ordinary calls in a row.
That is why every accept body is one line. The line is the first dispatch. The method name inside it (visit_add, not visit) is what makes the second dispatch work without an isinstance check.
A runnable example
Adding NodeCounter did not open Num, Add or Mul. Adding a fourth node type, say Neg, would open all three visitors. That is the trade the pattern makes.
The expression problem
Philip Wadler named this tension the expression problem in 1998: you want to add both new cases (types) and new functions (operations) without recompiling existing code, and keep static type safety. Plain objects and Visitor each solve half of it.
- Methods on each class — Edit every class — Add one class
- Visitor — Add one class — Edit every visitor
Pick by which one changes more often. Compilers, linters, formatters and document tools have a grammar that is fixed by a specification and a list of passes that grows for years, so they lean on Visitor. A domain model whose entities keep changing should not.
Alternatives in modern languages
Much of the boilerplate exists to get a second dispatch out of a language that only has one. Newer language features give you that dispatch directly.
- Python match statement — Branch on the node's class with case Add(left=l, right=r); added in Python 3.10 — PEP 634
- Python functools.singledispatch — A generic function that dispatches on the type of its first argument, with register per type — Python docs
- Java 21 pattern matching for switch — switch on a sealed interface; the compiler checks the cases are exhaustive from the permits clause — JEP 441
- Python ast.NodeVisitor — The standard library's own Visitor: visit() calls visit_ClassName or falls back to generic_visit() — Python docs
Here is the same expression tree with two of the operations written as match statements.
Same shape, no accept methods. What you lose is the compile-time guarantee that every operation handles every type, which Java's sealed switch keeps and Python does not. In an interview, offer the pattern, then say when the language makes it unnecessary.
What you will be able to do
- Say in one sentence what Visitor optimizes for, and what it makes harder.
- Explain double dispatch with the two calls, and why accept is always one line.
- Write an expression-tree visitor from memory and add a second operation without touching the nodes.
- Name the expression problem and place Visitor on the right side of its table.
- Offer pattern matching or single dispatch as the modern alternative, with the trade-off.
Interview questions
- Show the smell Visitor fixes, and the fix.
- Explain double dispatch. Why does Visitor need two calls?
- When is Visitor the wrong choice?
- What happens when you add a new element type to a Visitor design?
- How does Visitor relate to Composite and Iterator?
Sign up free to read the full topic
More Low-Level Design topics
Start free on Cariara · Coding problems · System design studio