liveBook · Manning

Visitor design pattern

As explained in Fabulous Adventures in Data Structures and Algorithms

The visitor pattern separates operations on a structure from the classes representing that structure by dispatching operations through visitor objects.

A parse tree may need to support pretty printing, type checking, optimization, and code generation. Putting a virtual method for every operation on every node would mix those concerns into the tree hierarchy. The visitor pattern keeps each operation in a separate class, while each node provides a small dispatch method. section 9.4 section 9.4.1

Dispatch takes two steps:

  • Define a visitor interface with one Visit overload per node type, such as Visit(Id) and Visit(Add). Its generic result type lets different visitors return different kinds of values. section 9.4.1
  • Give the base node an abstract Accept method and implement it in each derived node as visitor.Visit(this). A call to Accept on a base-typed value reaches the derived implementation, where this has the specific node type needed to select the overload. section 9.4.1
abstract record class Expr
{
    public abstract T Accept<T>(IExprVisitor<T> visitor);
}
sealed record class Id(string name) : Expr
{
    public override T Accept<T>(IExprVisitor<T> visitor) =>
        visitor.Visit(this);
}

Compare Expr.Accept with Id.Accept: the tree retains only the dispatch method; the operation-specific implementation belongs to the visitor. section 9.4.1

Pretty-printing an expression tree

An ExprToDoc visitor returns a Doc for each expression kind. An identifier becomes text; addition and multiplication share a binary-operator helper; calls assemble a parenthesized argument list. The resulting doc records grouping, indentation, and possible breaks for a later width-constrained formatting phase. section 9.4.2 section 9.5

The pattern does not eliminate changes to the tree: its node classes need Accept implementations. Once that dispatch machinery is present, further operations can live in new visitor classes rather than new virtual methods throughout the hierarchy. section 9.4.1 section 9.5

How Fabulous Adventures in Data Structures and Algorithms covers it

How deep it goes
Works through a generic visitor interface and Accept implementations, including overload resolution and runtime dispatch, then applies them to document construction.
Examples
Uses C# expression nodes and an ExprToDoc visitor for identifiers, binary operators, member access, and calls.
What it assumes
Assumes C# classes, interfaces, generics, and virtual methods while explaining why overload resolution alone cannot dispatch a base-typed node.
What you can do after
Build it from scratch: add Accept dispatch to a tree and implement a separate visitor that transforms expressions into formatting documents.
How it connects
Connects parse trees, virtual methods, overload resolution, interface dispatch, and pretty printing.

Also explained in

Compare how 4 books explain visitor design pattern →

Related concepts

This explainer was generated by Manning's knowledge graph from the text of Fabulous Adventures in Data Structures and Algorithms. The section links point to the parts of the book it draws on.