Skip to content

// Design Patterns · 23

Visitor

Many operations, fixed typesleads toAdd each as a visitor

Add operations to a stable set of types without editing them.

How it fits together

Visitor structureParagraphAccept(v): v.Visit(this)«interface»DocumentNodeAccept(visitor)TableAccept(v): v.Visit(this)«client»ExportJobAccept(new HtmlExport())HtmlExportwrites HTML«interface»DocumentVisitorVisit(Paragraph)Visit(Table)WordCountcounts words1 Accept(v)2 Visit(this)
uses / callsimplements· numbered steps replay in order
  • 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 readExpand

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.