← All Patterns
behavioral5 participants

Visitor

Represent an operation to be performed on elements of an object structure. Visitor lets you define a new operation without changing the classes of the elements on which it operates.

The Problem

You have a stable object structure (AST nodes, scene graph objects) and you need to add many unrelated operations over time — type checking, code generation, pretty printing, optimization. Adding each operation as a method to every node class pollutes the classes and requires recompiling the entire hierarchy for each new feature.

Structure

«interface»ASTVisitorvisit(NumberExpr&)visit(BinaryExpr&)visit(CallExpr&)TypeCheckerNumberExpraccept(v) → v.visit(*this)BinaryExpraccept(v) → v.visit(*this)visit(e)visit(e)double dispatchelement.accept(v) → v.visit(element)

Execution Walkthrough

1

Element interface

Each Element type declares accept(Visitor&). This is the only method elements need to know about visitors.

2

Visitor interface

3

Double dispatch

4

New operation

5

Walk structure

Code Comparison

Every new operation adds methods to every node class. Adding code generation touches every AST node file.

// BAD: operations scattered across node classes
struct NumberExpr {
  int value;
  void print()      { std::cout << value; }
  void type_check() { /* always int */ }
  std::string codegen() { return std::to_string(value); }
  // Add optimization? Add another method to EVERY node.
};

struct BinaryExpr {
  char op; ASTNode *lhs, *rhs;
  void print()      { lhs->print(); std::cout<<op; rhs->print(); }
  void type_check() { lhs->type_check(); rhs->type_check(); /*...*/ }
  std::string codegen() { /* ... */ }
  // Each new operation requires modifying all node types
};

Full C++ Implementation

C++ · Visitor
#include <iostream>
#include <stdexcept>
#include <memory>

struct NumberExpr; struct BinaryExpr;

struct ASTVisitor {
  virtual void visit(NumberExpr&) = 0;
  virtual void visit(BinaryExpr&) = 0;
  virtual ~ASTVisitor() = default;
};

struct ASTNode {
  virtual void accept(ASTVisitor&) = 0;
  virtual ~ASTNode() = default;
};

struct NumberExpr : ASTNode {
  int value;
  explicit NumberExpr(int v) : value(v) {}
  void accept(ASTVisitor& v) override { v.visit(*this); }
};

struct BinaryExpr : ASTNode {
  char      op;
  ASTNode*  lhs;
  ASTNode*  rhs;
  BinaryExpr(char o, ASTNode* l, ASTNode* r)
    : op(o), lhs(l), rhs(r) {}
  void accept(ASTVisitor& v) override { v.visit(*this); }
};

struct ASTPrinter : ASTVisitor {
  void visit(NumberExpr& n) override {
    std::cout << n.value;
  }
  void visit(BinaryExpr& b) override {
    std::cout << '(';
    b.lhs->accept(*this);
    std::cout << b.op;
    b.rhs->accept(*this);
    std::cout << ')';
  }
};

int main() {
  // AST for: (3 + 4) * 2
  NumberExpr three{3}, four{4}, two{2};
  BinaryExpr add{'+', &three, &four};
  BinaryExpr mul{'*', &add, &two};

  ASTPrinter printer;
  mul.accept(printer);   // prints: ((3+4)*2)
}

Participants

VisitorConcreteVisitorElementConcreteElementObjectStructure

Where it is used

Compilers (LLVM/Clang)

AST passes (type checker, const folder, codegen) are Visitors — new passes add no fields to AST nodes.

MDL Compiler

Semantic analysis, AST printer, and code emitter are three Visitors over the same parse tree.

XML/JSON processors

A SAX-style visitor walks the document tree; different visitors extract, validate, or transform.

Game engines

Scene graph Visitor computes bounding boxes, renders, or serializes without touching entity classes.

SingletonDecorator