Visitor

Allows adding new operations to object structures without modifying their classes.

Context

You have a stable AST of nodes (literal, add, multiply) and you want to add operations (evaluate, print, optimize) without touching the nodes.

Problem

Each new operation forces you to modify all classes in the AST.

Solution

Each node exposes Accept(visitor) and the visitor implements a method for each node type.

#Example in C#

public interface IExprVisitor<T>
{
    T VisitLiteral(Literal n);
    T VisitAdd(Add n);
}

public abstract record Expr { public abstract T Accept<T>(IExprVisitor<T> v); }
public record Literal(int Value) : Expr { public override T Accept<T>(IExprVisitor<T> v) => v.VisitLiteral(this); }
public record Add(Expr L, Expr R) : Expr { public override T Accept<T>(IExprVisitor<T> v) => v.VisitAdd(this); }

public class Evaluator : IExprVisitor<int>
{
    public int VisitLiteral(Literal n) => n.Value;
    public int VisitAdd(Add n)         => n.L.Accept(this) + n.R.Accept(this);
}

In modern C#, pattern matching over records is often simpler than a full Visitor.

When NOT to use it
  • When the hierarchy changes frequently (each change breaks ALL visitors).
  • When a switch expression with pattern matching already covers the operation.
Tradeoffs
Pro Con
Add operations without touching nodes Adding nodes breaks all visitors

#behavioral #gof #double-dispatch