// Design Patterns · 23
Visitor
Add operations to a stable set of types without editing them.
How it fits together
- ExportJob uses / calls DocumentNode (1 Accept(v))
- Paragraph implements DocumentNode
- Table implements DocumentNode
- Paragraph uses / calls DocumentVisitor (2 Visit(this))
- HtmlExport implements DocumentVisitor
- WordCount implements DocumentVisitor
Spot it
- Type checks over the same node types everywhere
- Each new report edits every element class
- Operations unrelated to the types bloat them
It costs
New operations are cheap; new element types touch every visitor.
In code
public interface IDocumentVisitor{ void Visit(Paragraph paragraph); void Visit(Table table);}public interface IDocumentNode { void Accept(IDocumentVisitor visitor); }public sealed record Paragraph(string Text) : IDocumentNode{ public void Accept(IDocumentVisitor visitor) => visitor.Visit(this);}public sealed record Table(IReadOnlyList<string[]> Rows) : IDocumentNode{ public void Accept(IDocumentVisitor visitor) => visitor.Visit(this);}public sealed class WordCount : IDocumentVisitor{ public int Total { get; private set; } public void Visit(Paragraph paragraph) => Total += paragraph.Text.Split(' ').Length; public void Visit(Table table) => Total += table.Rows.Sum(row => row.Sum(cell => cell.Split(' ').Length));}Go deeperThe full explanation and what to ask for · 1 min readExpandCollapse
Visitor uses double dispatch: the client calls element.Accept(visitor), and each element calls back visitor.Visit(this), so the method that runs depends on both the element type and the operation. Each operation lives in one visitor class instead of being spread across the elements.
It suits a stable set of types with a growing list of operations: syntax trees, document models, compilers. If the types change often, every visitor changes with them. In languages with exhaustive pattern matching over a closed set of types, a switch expression often gives the same compile-time safety with less ceremony.
Visitor usually walks a Composite or an Interpreter's expression tree. Iterator hands you elements one at a time; Visitor gives you a type-specific operation per element. It is the mirror of plain polymorphism, which makes new types cheap and new operations expensive.
Ask for it
- visitor
- element
- double dispatch
- accept / visit
- expression problem
- exhaustive match
Export and word count each switch over the document node types (Paragraph, Table, Image). Add a DocumentVisitor with one Visit per node type and Accept(visitor) on each node, then move each operation into its own visitor, so a new node type fails to compile until every visitor handles it.