BehavioralTypeScriptverifiedVerified
Visitor Pattern in TypeScript
Lets you add new operations to an object structure without modifying the objects themselves, by separating the algorithm from the object structure it operates on.
How to Implement the Visitor Pattern in TypeScript
1Step 1: Define the Visitor and Element interfaces
interface ShapeVisitor {
visitCircle(shape: Circle): number;
visitRectangle(shape: Rectangle): number;
visitTriangle(shape: Triangle): number;
}
interface Shape {
accept<T>(visitor: { visitCircle(s: Circle): T; visitRectangle(s: Rectangle): T; visitTriangle(s: Triangle): T }): T;
}2Step 2: Implement concrete elements with accept methods
class Circle implements Shape {
constructor(public radius: number) {}
accept<T>(visitor: { visitCircle(s: Circle): T; visitRectangle(s: Rectangle): T; visitTriangle(s: Triangle): T }): T {
return visitor.visitCircle(this);
}
}
class Rectangle implements Shape {
constructor(public width: number, public height: number) {}
accept<T>(visitor: { visitCircle(s: Circle): T; visitRectangle(s: Rectangle): T; visitTriangle(s: Triangle): T }): T {
return visitor.visitRectangle(this);
}
}
class Triangle implements Shape {
constructor(public base: number, public height: number, public sideA: number, public sideB: number) {}
accept<T>(visitor: { visitCircle(s: Circle): T; visitRectangle(s: Rectangle): T; visitTriangle(s: Triangle): T }): T {
return visitor.visitTriangle(this);
}
}3Step 3: Create visitors for area and perimeter calculations
const areaVisitor: ShapeVisitor = {
visitCircle: s => Math.PI * s.radius ** 2,
visitRectangle: s => s.width * s.height,
visitTriangle: s => 0.5 * s.base * s.height,
};
const perimeterVisitor: ShapeVisitor = {
visitCircle: s => 2 * Math.PI * s.radius,
visitRectangle: s => 2 * (s.width + s.height),
visitTriangle: s => s.base + s.sideA + s.sideB,
};4Step 4: Apply visitors to a collection of shapes
const shapes: Shape[] = [new Circle(5), new Rectangle(4, 6), new Triangle(3, 4, 3, 5)];
console.log("Areas:", shapes.map(s => s.accept(areaVisitor)));
console.log("Perimeters:", shapes.map(s => s.accept(perimeterVisitor)));// ── AST Visitor for Code Analysis ─────────────────────────────────
// ── AST Node Types ────────────────────────────────────────────────
interface ASTVisitor<T = void> {
visitProgram(node: ProgramNode): T;
visitFunctionDecl(node: FunctionDeclNode): T;
visitVariableDecl(node: VariableDeclNode): T;
visitCallExpression(node: CallExpressionNode): T;
visitReturnStatement(node: ReturnStatementNode): T;
}
interface ASTNode {
kind: string;
accept<T>(visitor: ASTVisitor<T>): T;
}
class ProgramNode implements ASTNode {
readonly kind = "Program";
constructor(public body: ASTNode[]) {}
accept<T>(v: ASTVisitor<T>): T { return v.visitProgram(this); }
}
class FunctionDeclNode implements ASTNode {
readonly kind = "FunctionDecl";
constructor(
public name: string,
public params: string[],
public body: ASTNode[],
public isAsync: boolean = false
) {}
accept<T>(v: ASTVisitor<T>): T { return v.visitFunctionDecl(this); }
}
class VariableDeclNode implements ASTNode {
readonly kind = "VariableDecl";
constructor(
public name: string,
public kind_: "let" | "const" | "var",
public init: ASTNode | null = null
) {}
accept<T>(v: ASTVisitor<T>): T { return v.visitVariableDecl(this); }
}
class CallExpressionNode implements ASTNode {
readonly kind = "CallExpression";
constructor(public callee: string, public args: ASTNode[]) {}
accept<T>(v: ASTVisitor<T>): T { return v.visitCallExpression(this); }
}
class ReturnStatementNode implements ASTNode {
readonly kind = "ReturnStatement";
constructor(public argument: ASTNode | null = null) {}
accept<T>(v: ASTVisitor<T>): T { return v.visitReturnStatement(this); }
}
// ── Visitors ──────────────────────────────────────────────────────
interface CodeMetrics {
functionCount: number;
asyncFunctionCount: number;
variableCount: number;
varUsageCount: number;
callCount: number;
returnCount: number;
}
class MetricsVisitor implements ASTVisitor<void> {
metrics: CodeMetrics = {
functionCount: 0,
asyncFunctionCount: 0,
variableCount: 0,
varUsageCount: 0,
callCount: 0,
returnCount: 0,
};
visitProgram(node: ProgramNode): void {
node.body.forEach(n => n.accept(this));
}
visitFunctionDecl(node: FunctionDeclNode): void {
this.metrics.functionCount++;
if (node.isAsync) this.metrics.asyncFunctionCount++;
node.body.forEach(n => n.accept(this));
}
visitVariableDecl(node: VariableDeclNode): void {
this.metrics.variableCount++;
if (node.kind_ === "var") this.metrics.varUsageCount++;
node.init?.accept(this);
}
visitCallExpression(node: CallExpressionNode): void {
this.metrics.callCount++;
node.args.forEach(a => a.accept(this));
}
visitReturnStatement(node: ReturnStatementNode): void {
this.metrics.returnCount++;
node.argument?.accept(this);
}
}
class PrettyPrintVisitor implements ASTVisitor<string> {
private indent = 0;
private pad(): string { return " ".repeat(this.indent); }
visitProgram(node: ProgramNode): string {
return node.body.map(n => n.accept(this)).join("\n");
}
visitFunctionDecl(node: FunctionDeclNode): string {
const prefix = node.isAsync ? "async function" : "function";
this.indent++;
const body = node.body.map(n => n.accept(this)).join("\n");
this.indent--;
return `${this.pad()}${prefix} ${node.name}(${node.params.join(", ")}) {\n${body}\n${this.pad()}}`;
}
visitVariableDecl(node: VariableDeclNode): string {
const init = node.init ? ` = ${node.init.accept(this)}` : "";
return `${this.pad()}${node.kind_} ${node.name}${init};`;
}
visitCallExpression(node: CallExpressionNode): string {
const args = node.args.map(a => a.accept(this)).join(", ");
return `${node.callee}(${args})`;
}
visitReturnStatement(node: ReturnStatementNode): string {
const value = node.argument ? ` ${node.argument.accept(this)}` : "";
return `${this.pad()}return${value};`;
}
}
export {
MetricsVisitor,
PrettyPrintVisitor,
ProgramNode,
FunctionDeclNode,
VariableDeclNode,
CallExpressionNode,
ReturnStatementNode,
type ASTVisitor,
type ASTNode,
type CodeMetrics,
};Visitor Pattern Architecture
hourglass_empty
Rendering diagram...
lightbulb
Visitor Pattern in the Real World
“Think of a tax auditor visiting different types of businesses—a restaurant, a law firm, a retail shop. The auditor (visitor) knows exactly what to examine at each type of business and applies the appropriate inspection procedure. The businesses (elements) simply let the auditor in; they don't change their own operations to accommodate the audit.”