SwadhyAI Notes

All Low-Level Design notes

Behavioral · Gang of Four

Visitor

Visitor is a behavioral pattern that adds a new operation over a fixed set of types without editing those types each time. Each type accepts the visitor; the visitor implements visit for each type.

Example: a folder tree of files and folders. Pretty-print is a method on both. Then finance wants tax, and ops wants a zip export. Each new walk edits File and Folder. Use Visitor when the types are stable and the operations keep growing.

Types × operations

Freeze the columns, grow the rows

: File / Folder. Growing walks: PrettyPrint, Tax, ZipExport. A new is a new class, not a new method on File. That is also OCP aimed at the nodes.

Walk \ NodeFileFolder
Pretty-printprint nameprint then children
Taxadd 18% of pricesum children
Zip exportwrite bytesenter directory
next levynew visitor methodnew visitor method

node.accept(visitor) → visitor.visit(this)

The right visit method runs because the node knows its own type and passes this. That is the whole trick. In an interview, one visitor that pretty-prints and one that sums tax is enough.

Diagram text: the exact Mermaid definition below preserves the nodes, labels, and connections. Enable JavaScript to view the rendered diagram.
Read the diagram definition
sequenceDiagram
    participant C as Client
    participant F as FileNode
    participant V as TaxVisitor

    C->>F: accept(taxVisitor)
    F->>V: visit(this)
    V->>V: add 18% of price
01

Freeze the node types

Typical type sets are File and Folder, or a small expression AST. New types should be rare once Visitor is in place.

02

Give each node accept

accept(Visitor v) { v.visit(this); }

03

Add operations as visitors

PrettyPrint, Tax, ZipExport — new classes, not new methods on File.

Composite gives you the tree

Composite treats File and Folder the same for structure. Visitor gives you new operations over that tree. An in-memory file system often wants both.

Interpreter evaluates a grammar

Interpreter is matches() on a filter tree. Visitor is a new pass over a tree you already have. Do not call a TaxVisitor an interpreter.

Java: tax without editing every node

Folder’s visit recurses with accept. File’s visit does the leaf work. A third operation is another class. FileNode does not change.

java
interface Node {
  void accept(Visitor v);
}

class FileNode implements Node {
  final String name;
  final Money price;
  public void accept(Visitor v) { v.visit(this); }
}

class FolderNode implements Node {
  final List<Node> children;
  public void accept(Visitor v) { v.visit(this); }
}

interface Visitor {
  void visit(FileNode file);
  void visit(FolderNode folder);
}

class TaxVisitor implements Visitor {
  Money total = Money.zero();
  public void visit(FileNode file) {
    total = total.plus(file.price.times("0.18"));
  }
  public void visit(FolderNode folder) {
    folder.children.forEach(c -> c.accept(this));
  }
}

class PrettyPrint implements Visitor {
  public void visit(FileNode file) { System.out.println(file.name); }
  public void visit(FolderNode folder) {
    folder.children.forEach(c -> c.accept(this));
  }
}

Visitor when the types are the thing that grows

A new widget type every sprint will break every visitor. Then you wanted a method on the node, or a strategy the node holds. Visitor bets that operations grow and types do not.

Adding export() and tax() on File and Folder

That is the smell Visitor exists to fix. Two operations is already a reason to show accept. The third operation is when the interviewer nods.

When the types grow instead

A new node type every week breaks every visitor. Then a method on the node, or a strategy the node holds, is the honest shape. Visitor is a bet: operations grow, types do not. State that trade-off clearly.

Use Visitor

File system, expression AST, document model. Finance keeps inventing levies. Ops keeps inventing exports.

Do not

UI widget zoo, payment providers that appear monthly, vehicle types in a parking lot. Those types are the axis that moves.

Java: expression tree size

Interpreter evaluates. A SizeVisitor is a second pass. NumberExpr and AddExpr stay closed.

java
interface Expr {
  void accept(ExprVisitor v);
}

class NumberExpr implements Expr {
  final int value;
  public void accept(ExprVisitor v) { v.visit(this); }
}

class AddExpr implements Expr {
  final Expr left, right;
  public void accept(ExprVisitor v) { v.visit(this); }
}

interface ExprVisitor {
  void visit(NumberExpr n);
  void visit(AddExpr a);
}

class SizeVisitor implements ExprVisitor {
  int size;
  public void visit(NumberExpr n) { size++; }
  public void visit(AddExpr a) { a.left.accept(this); a.right.accept(this); size++; }
}

Do not raid privates

Tax should call price(). If the visitor needs the file’s byte buffer, you either made a public question or you are writing a God visitor. Prefer a method the node already had.

One visitor, one walk

PrettyPrint and Tax are two classes. A Visitor that prints and taxes and zips is the God object you just moved off File.

Nearby in LLD