Visitor Pattern
Visitor Pattern: Operations on Object Structures
Section titled “Visitor Pattern: Operations on Object Structures”Now let’s dive into the Visitor Pattern - one of the most sophisticated behavioral design patterns that lets you define new operations on objects without changing their classes. It separates algorithms from the object structure they operate on.
Why Visitor Pattern?
Section titled “Why Visitor Pattern?”Imagine a document editor with different elements - paragraphs, images, tables. You need multiple operations: export to PDF, export to HTML, calculate word count, spell check. Without Visitor, you’d add methods to each element for each operation. With Visitor Pattern, you create separate visitor classes for each operation, keeping elements clean!
The Visitor Pattern lets you add new operations to a class hierarchy without modifying the classes. You define a visitor object that implements all variations of an operation for each class in the hierarchy, and the elements “accept” visitors to let them perform operations.
What’s the Use of Visitor Pattern?
Section titled “What’s the Use of Visitor Pattern?”The Visitor Pattern is useful when:
- You need many unrelated operations - On objects in a structure
- Object structure rarely changes - But operations change often
- You want to avoid polluting classes - Keep operations separate
- You need operations across class hierarchy - Different handling per type
- You want to accumulate state - Visitor can gather information as it visits
What Happens If We Don’t Use Visitor Pattern?
Section titled “What Happens If We Don’t Use Visitor Pattern?”Without the Visitor Pattern, you might:
- Add methods to every class - For each new operation
- Violate Single Responsibility - Classes do too much
- Violate Open/Closed - Modify classes for new operations
- Scatter related code - Export logic spread across classes
- Hard to add operations - Need to modify all classes
Simple Example: The Shape Calculator
Section titled “Simple Example: The Shape Calculator”Let’s start with a super simple example that anyone can understand!
Visual Representation
Section titled “Visual Representation”Double Dispatch Flow
Section titled “Double Dispatch Flow”Here’s how the Visitor Pattern achieves double dispatch:
The Problem
Section titled “The Problem”You’re building a graphics application with different shapes. You need to calculate area, perimeter, and draw operations. Without Visitor Pattern:
# ❌ Without Visitor Pattern - Operations in every class!
import math
class Circle: def __init__(self, radius: float): self.radius = radius
def area(self) -> float: return math.pi * self.radius ** 2
def perimeter(self) -> float: return 2 * math.pi * self.radius
def draw(self) -> str: return f"Drawing circle with radius {self.radius}"
# Adding new operation? Add method here! def export_svg(self) -> str: return f'<circle r="{self.radius}"/>'
class Rectangle: def __init__(self, width: float, height: float): self.width = width self.height = height
def area(self) -> float: return self.width * self.height
def perimeter(self) -> float: return 2 * (self.width + self.height)
def draw(self) -> str: return f"Drawing rectangle {self.width}x{self.height}"
# Adding new operation? Add method here too! def export_svg(self) -> str: return f'<rect width="{self.width}" height="{self.height}"/>'
class Triangle: def __init__(self, a: float, b: float, c: float): self.a, self.b, self.c = a, b, c
def area(self) -> float: s = (self.a + self.b + self.c) / 2 return math.sqrt(s * (s-self.a) * (s-self.b) * (s-self.c))
def perimeter(self) -> float: return self.a + self.b + self.c
def draw(self) -> str: return f"Drawing triangle with sides {self.a}, {self.b}, {self.c}"
# And here... for EVERY shape! def export_svg(self) -> str: return f'<polygon points="..."/>'
# Problems:# - Every new operation = modify ALL shape classes# - Related code (all area calcs) spread across files# - Shapes know about SVG, drawing, etc. (too much responsibility)# - Violates Open/Closed Principle// ❌ Without Visitor Pattern - Operations in every class!
class Circle { private double radius;
public Circle(double radius) { this.radius = radius; }
public double area() { return Math.PI * radius * radius; }
public double perimeter() { return 2 * Math.PI * radius; }
public String draw() { return "Drawing circle with radius " + radius; }
// Adding new operation? Add method here! public String exportSvg() { return "<circle r=\"" + radius + "\"/>"; }}
class Rectangle { private double width, height;
public Rectangle(double width, double height) { this.width = width; this.height = height; }
public double area() { return width * height; }
public double perimeter() { return 2 * (width + height); }
public String draw() { return "Drawing rectangle " + width + "x" + height; }
// Adding new operation? Add method here too! public String exportSvg() { return "<rect width=\"" + width + "\" height=\"" + height + "\"/>"; }}
// Problems:// - Every new operation = modify ALL shape classes// - Related code (all area calcs) spread across files// - Shapes know about SVG, drawing, etc. (too much responsibility)// - Violates Open/Closed Principle// ❌ Without Visitor Pattern - Operations in every class!
class Circle { constructor(private radius: number) {}
area(): number { return Math.PI * this.radius ** 2; }
perimeter(): number { return 2 * Math.PI * this.radius; }
draw(): string { return `Drawing circle with radius ${this.radius}`; }
// Adding new operation? Add method here! exportSvg(): string { return `<circle r="${this.radius}"/>`; }}
class Rectangle { constructor(private width: number, private height: number) {}
area(): number { return this.width * this.height; }
perimeter(): number { return 2 * (this.width + this.height); }
draw(): string { return `Drawing rectangle ${this.width}x${this.height}`; }
// Adding new operation? Add method here too! exportSvg(): string { return `<rect width="${this.width}" height="${this.height}"/>`; }}
class Triangle { constructor(private a: number, private b: number, private c: number) {}
area(): number { const s = (this.a + this.b + this.c) / 2; return Math.sqrt(s * (s-this.a) * (s-this.b) * (s-this.c)); }
perimeter(): number { return this.a + this.b + this.c; }
draw(): string { return `Drawing triangle with sides ${this.a}, ${this.b}, ${this.c}`; }
// And here... for EVERY shape! exportSvg(): string { return '<polygon points="..."/>'; }}
// Problems:// - Every new operation = modify ALL shape classes// - Related code (all area calcs) spread across files// - Shapes know about SVG, drawing, etc. (too much responsibility)// - Violates Open/Closed Principle// ❌ Without Visitor Pattern - Operations in every class!
#include <iostream>#include <string>#include <cmath>
class Circle {private: double radius;
public: Circle(double r) : radius(r) {}
double area() { return M_PI * radius * radius; }
double perimeter() { return 2 * M_PI * radius; }
std::string draw() { return "Drawing circle with radius " + std::to_string(radius); }
// Adding new operation? Add method here! std::string exportSvg() { return "<circle r=\"" + std::to_string(radius) + "\"/>"; }};
class Rectangle {private: double width, height;
public: Rectangle(double w, double h) : width(w), height(h) {}
double area() { return width * height; }
double perimeter() { return 2 * (width + height); }
std::string draw() { return "Drawing rectangle " + std::to_string(width) + "x" + std::to_string(height); }
// Adding new operation? Add method here too! std::string exportSvg() { return "<rect width=\"" + std::to_string(width) + "\" height=\"" + std::to_string(height) + "\"/>"; }};
class Triangle {private: double a, b, c;
public: Triangle(double a, double b, double c) : a(a), b(b), c(c) {}
double area() { double s = (a + b + c) / 2; return std::sqrt(s * (s-a) * (s-b) * (s-c)); }
double perimeter() { return a + b + c; }
std::string draw() { return "Drawing triangle with sides " + std::to_string(a) + ", " + std::to_string(b) + ", " + std::to_string(c); }
// And here... for EVERY shape! std::string exportSvg() { return "<polygon points=\"...\"/>"; }};
// Problems:// - Every new operation = modify ALL shape classes// - Related code (all area calcs) spread across files// - Shapes know about SVG, drawing, etc. (too much responsibility)// - Violates Open/Closed Principle// ❌ Without Visitor Pattern - Operations in every class!
using System;
class Circle{ private double radius;
public Circle(double radius) { this.radius = radius; }
public double Area() { return Math.PI * radius * radius; }
public double Perimeter() { return 2 * Math.PI * radius; }
public string Draw() { return $"Drawing circle with radius {radius}"; }
// Adding new operation? Add method here! public string ExportSvg() { return $"<circle r=\"{radius}\"/>"; }}
class Rectangle{ private double width, height;
public Rectangle(double width, double height) { this.width = width; this.height = height; }
public double Area() { return width * height; }
public double Perimeter() { return 2 * (width + height); }
public string Draw() { return $"Drawing rectangle {width}x{height}"; }
// Adding new operation? Add method here too! public string ExportSvg() { return $"<rect width=\"{width}\" height=\"{height}\"/>"; }}
class Triangle{ private double a, b, c;
public Triangle(double a, double b, double c) { this.a = a; this.b = b; this.c = c; }
public double Area() { double s = (a + b + c) / 2; return Math.Sqrt(s * (s-a) * (s-b) * (s-c)); }
public double Perimeter() { return a + b + c; }
public string Draw() { return $"Drawing triangle with sides {a}, {b}, {c}"; }
// And here... for EVERY shape! public string ExportSvg() { return "<polygon points=\"...\"/>"; }}
// Problems:// - Every new operation = modify ALL shape classes// - Related code (all area calcs) spread across files// - Shapes know about SVG, drawing, etc. (too much responsibility)// - Violates Open/Closed Principlepackage main
import ( "fmt" "math")
// ❌ Without Visitor Pattern - Operations in every class!
type Circle struct{ Radius float64 }
func (c *Circle) Area() float64 { return math.Pi * c.Radius * c.Radius }func (c *Circle) Perimeter() float64 { return 2 * math.Pi * c.Radius }func (c *Circle) ExportSVG() string { return fmt.Sprintf(`<circle r="%.1f"/>`, c.Radius) }// Adding new operation? Add method here AND in every shape!
type Rectangle struct{ Width, Height float64 }
func (r *Rectangle) Area() float64 { return r.Width * r.Height }func (r *Rectangle) Perimeter() float64 { return 2 * (r.Width + r.Height) }func (r *Rectangle) ExportSVG() string { return fmt.Sprintf(`<rect width="%.1f" height="%.1f"/>`, r.Width, r.Height)}
// Problems: every new operation = modify ALL shape classes!// The Problemenum Shape { Circle(f64), Rectangle(f64, f64),}impl Shape { fn area(&self) -> f64 { match self { Shape::Circle(r) => 3.14 * r * r, Shape::Rectangle(w, h) => w * h, } } fn export(&self) { println!("export shape"); }}Problems:
- Every new operation requires modifying ALL classes
- Related operation code scattered across files
- Classes have too many responsibilities
- Violates Open/Closed Principle
The Solution: Visitor Pattern
Section titled “The Solution: Visitor Pattern”Class Structure
Section titled “Class Structure”from abc import ABC, abstractmethodimport mathfrom typing import List
# Step 1: Define the Visitor interfaceclass ShapeVisitor(ABC): """Visitor interface - defines visit methods for each shape type"""
@abstractmethod def visit_circle(self, circle: 'Circle') -> None: pass
@abstractmethod def visit_rectangle(self, rectangle: 'Rectangle') -> None: pass
@abstractmethod def visit_triangle(self, triangle: 'Triangle') -> None: pass
# Step 2: Define the Element interfaceclass Shape(ABC): """Element interface - shapes accept visitors"""
@abstractmethod def accept(self, visitor: ShapeVisitor) -> None: """Accept a visitor to perform an operation""" pass
# Step 3: Create Concrete Elementsclass Circle(Shape): """Concrete element - circle shape"""
def __init__(self, radius: float): self.radius = radius
def accept(self, visitor: ShapeVisitor) -> None: visitor.visit_circle(self)
class Rectangle(Shape): """Concrete element - rectangle shape"""
def __init__(self, width: float, height: float): self.width = width self.height = height
def accept(self, visitor: ShapeVisitor) -> None: visitor.visit_rectangle(self)
class Triangle(Shape): """Concrete element - triangle shape"""
def __init__(self, a: float, b: float, c: float): self.a = a self.b = b self.c = c
def accept(self, visitor: ShapeVisitor) -> None: visitor.visit_triangle(self)
# Step 4: Create Concrete Visitorsclass AreaCalculator(ShapeVisitor): """Visitor that calculates total area"""
def __init__(self): self.total_area = 0.0
def visit_circle(self, circle: Circle) -> None: area = math.pi * circle.radius ** 2 self.total_area += area print(f" ⭕ Circle area: {area:.2f}")
def visit_rectangle(self, rectangle: Rectangle) -> None: area = rectangle.width * rectangle.height self.total_area += area print(f" ⬜ Rectangle area: {area:.2f}")
def visit_triangle(self, triangle: Triangle) -> None: s = (triangle.a + triangle.b + triangle.c) / 2 area = math.sqrt(s * (s-triangle.a) * (s-triangle.b) * (s-triangle.c)) self.total_area += area print(f" 🔺 Triangle area: {area:.2f}")
def get_total(self) -> float: return self.total_area
class PerimeterCalculator(ShapeVisitor): """Visitor that calculates total perimeter"""
def __init__(self): self.total_perimeter = 0.0
def visit_circle(self, circle: Circle) -> None: perimeter = 2 * math.pi * circle.radius self.total_perimeter += perimeter print(f" ⭕ Circle perimeter: {perimeter:.2f}")
def visit_rectangle(self, rectangle: Rectangle) -> None: perimeter = 2 * (rectangle.width + rectangle.height) self.total_perimeter += perimeter print(f" ⬜ Rectangle perimeter: {perimeter:.2f}")
def visit_triangle(self, triangle: Triangle) -> None: perimeter = triangle.a + triangle.b + triangle.c self.total_perimeter += perimeter print(f" 🔺 Triangle perimeter: {perimeter:.2f}")
def get_total(self) -> float: return self.total_perimeter
class SVGExporter(ShapeVisitor): """Visitor that exports shapes to SVG"""
def __init__(self): self.elements: List[str] = []
def visit_circle(self, circle: Circle) -> None: svg = f'<circle cx="50" cy="50" r="{circle.radius}" fill="blue"/>' self.elements.append(svg) print(f" ⭕ Exported circle to SVG")
def visit_rectangle(self, rectangle: Rectangle) -> None: svg = f'<rect x="10" y="10" width="{rectangle.width}" height="{rectangle.height}" fill="green"/>' self.elements.append(svg) print(f" ⬜ Exported rectangle to SVG")
def visit_triangle(self, triangle: Triangle) -> None: svg = f'<polygon points="50,10 10,90 90,90" fill="red"/>' self.elements.append(svg) print(f" 🔺 Exported triangle to SVG")
def get_svg(self) -> str: elements = "\n ".join(self.elements) return f'<svg xmlns="http://www.w3.org/2000/svg">\n {elements}\n</svg>'
# Step 5: Use the patterndef main(): print("=" * 60) print("Shape Calculator (Visitor Pattern)") print("=" * 60)
# Create shapes shapes: List[Shape] = [ Circle(5), Rectangle(4, 6), Triangle(3, 4, 5), Circle(3), ]
# Calculate areas print("\n📐 Calculating Areas:") print("-" * 40) area_calc = AreaCalculator() for shape in shapes: shape.accept(area_calc) print(f"\n 📊 Total Area: {area_calc.get_total():.2f}")
# Calculate perimeters print("\n📏 Calculating Perimeters:") print("-" * 40) perimeter_calc = PerimeterCalculator() for shape in shapes: shape.accept(perimeter_calc) print(f"\n 📊 Total Perimeter: {perimeter_calc.get_total():.2f}")
# Export to SVG print("\n🎨 Exporting to SVG:") print("-" * 40) svg_exporter = SVGExporter() for shape in shapes: shape.accept(svg_exporter) print(f"\n 📄 SVG Output:\n{svg_exporter.get_svg()}")
# Easy to add new operation - just create new visitor! print("\n" + "=" * 60) print("✅ Visitor Pattern: New operations without modifying shapes!") print("✅ Adding new operation = Just one new visitor class!")
if __name__ == "__main__": main()import java.util.*;
// Step 1: Define the Visitor interfaceinterface ShapeVisitor { /** * Visitor interface - defines visit methods for each shape type */ void visitCircle(Circle circle); void visitRectangle(Rectangle rectangle); void visitTriangle(Triangle triangle);}
// Step 2: Define the Element interfaceinterface Shape { /** * Element interface - shapes accept visitors */ void accept(ShapeVisitor visitor);}
// Step 3: Create Concrete Elementsclass Circle implements Shape { /** * Concrete element - circle shape */ private double radius;
public Circle(double radius) { this.radius = radius; }
public double getRadius() { return radius; }
@Override public void accept(ShapeVisitor visitor) { visitor.visitCircle(this); }}
class Rectangle implements Shape { /** * Concrete element - rectangle shape */ private double width; private double height;
public Rectangle(double width, double height) { this.width = width; this.height = height; }
public double getWidth() { return width; } public double getHeight() { return height; }
@Override public void accept(ShapeVisitor visitor) { visitor.visitRectangle(this); }}
class Triangle implements Shape { /** * Concrete element - triangle shape */ private double a, b, c;
public Triangle(double a, double b, double c) { this.a = a; this.b = b; this.c = c; }
public double getA() { return a; } public double getB() { return b; } public double getC() { return c; }
@Override public void accept(ShapeVisitor visitor) { visitor.visitTriangle(this); }}
// Step 4: Create Concrete Visitorsclass AreaCalculator implements ShapeVisitor { /** * Visitor that calculates total area */ private double totalArea = 0.0;
@Override public void visitCircle(Circle circle) { double area = Math.PI * circle.getRadius() * circle.getRadius(); totalArea += area; System.out.printf(" ⭕ Circle area: %.2f%n", area); }
@Override public void visitRectangle(Rectangle rectangle) { double area = rectangle.getWidth() * rectangle.getHeight(); totalArea += area; System.out.printf(" ⬜ Rectangle area: %.2f%n", area); }
@Override public void visitTriangle(Triangle triangle) { double s = (triangle.getA() + triangle.getB() + triangle.getC()) / 2; double area = Math.sqrt(s * (s - triangle.getA()) * (s - triangle.getB()) * (s - triangle.getC())); totalArea += area; System.out.printf(" 🔺 Triangle area: %.2f%n", area); }
public double getTotal() { return totalArea; }}
class PerimeterCalculator implements ShapeVisitor { /** * Visitor that calculates total perimeter */ private double totalPerimeter = 0.0;
@Override public void visitCircle(Circle circle) { double perimeter = 2 * Math.PI * circle.getRadius(); totalPerimeter += perimeter; System.out.printf(" ⭕ Circle perimeter: %.2f%n", perimeter); }
@Override public void visitRectangle(Rectangle rectangle) { double perimeter = 2 * (rectangle.getWidth() + rectangle.getHeight()); totalPerimeter += perimeter; System.out.printf(" ⬜ Rectangle perimeter: %.2f%n", perimeter); }
@Override public void visitTriangle(Triangle triangle) { double perimeter = triangle.getA() + triangle.getB() + triangle.getC(); totalPerimeter += perimeter; System.out.printf(" 🔺 Triangle perimeter: %.2f%n", perimeter); }
public double getTotal() { return totalPerimeter; }}
class SVGExporter implements ShapeVisitor { /** * Visitor that exports shapes to SVG */ private List<String> elements = new ArrayList<>();
@Override public void visitCircle(Circle circle) { String svg = String.format( "<circle cx=\"50\" cy=\"50\" r=\"%.0f\" fill=\"blue\"/>", circle.getRadius()); elements.add(svg); System.out.println(" ⭕ Exported circle to SVG"); }
@Override public void visitRectangle(Rectangle rectangle) { String svg = String.format( "<rect x=\"10\" y=\"10\" width=\"%.0f\" height=\"%.0f\" fill=\"green\"/>", rectangle.getWidth(), rectangle.getHeight()); elements.add(svg); System.out.println(" ⬜ Exported rectangle to SVG"); }
@Override public void visitTriangle(Triangle triangle) { String svg = "<polygon points=\"50,10 10,90 90,90\" fill=\"red\"/>"; elements.add(svg); System.out.println(" 🔺 Exported triangle to SVG"); }
public String getSvg() { String content = String.join("\n ", elements); return "<svg xmlns=\"http://www.w3.org/2000/svg\">\n " + content + "\n</svg>"; }}
// Step 5: Use the patternpublic class Main { public static void main(String[] args) { System.out.println("=".repeat(60)); System.out.println("Shape Calculator (Visitor Pattern)"); System.out.println("=".repeat(60));
// Create shapes List<Shape> shapes = Arrays.asList( new Circle(5), new Rectangle(4, 6), new Triangle(3, 4, 5), new Circle(3) );
// Calculate areas System.out.println("\n📐 Calculating Areas:"); System.out.println("-".repeat(40)); AreaCalculator areaCalc = new AreaCalculator(); for (Shape shape : shapes) { shape.accept(areaCalc); } System.out.printf("%n 📊 Total Area: %.2f%n", areaCalc.getTotal());
// Calculate perimeters System.out.println("\n📏 Calculating Perimeters:"); System.out.println("-".repeat(40)); PerimeterCalculator perimeterCalc = new PerimeterCalculator(); for (Shape shape : shapes) { shape.accept(perimeterCalc); } System.out.printf("%n 📊 Total Perimeter: %.2f%n", perimeterCalc.getTotal());
// Export to SVG System.out.println("\n🎨 Exporting to SVG:"); System.out.println("-".repeat(40)); SVGExporter svgExporter = new SVGExporter(); for (Shape shape : shapes) { shape.accept(svgExporter); } System.out.println("\n 📄 SVG Output:\n" + svgExporter.getSvg());
System.out.println("\n" + "=".repeat(60)); System.out.println("✅ Visitor Pattern: New operations without modifying shapes!"); System.out.println("✅ Adding new operation = Just one new visitor class!"); }}// Complete TypeScript implementation showing key structure
// Step 1: Define the Visitor interfaceinterface ShapeVisitor { visitCircle(circle: Circle): void; visitRectangle(rectangle: Rectangle): void; visitTriangle(triangle: Triangle): void;}
// Step 2: Define the Element interfaceinterface Shape { accept(visitor: ShapeVisitor): void;}
// Step 3: Create Concrete Elementsclass Circle implements Shape { constructor(public radius: number) {}
accept(visitor: ShapeVisitor): void { visitor.visitCircle(this); }}
class Rectangle implements Shape { constructor(public width: number, public height: number) {}
accept(visitor: ShapeVisitor): void { visitor.visitRectangle(this); }}
class Triangle implements Shape { constructor(public a: number, public b: number, public c: number) {}
accept(visitor: ShapeVisitor): void { visitor.visitTriangle(this); }}
// Step 4: Create Concrete Visitorsclass AreaCalculator implements ShapeVisitor { private totalArea = 0;
visitCircle(circle: Circle): void { const area = Math.PI * circle.radius ** 2; this.totalArea += area; console.log(` ⭕ Circle area: ${area.toFixed(2)}`); }
visitRectangle(rectangle: Rectangle): void { const area = rectangle.width * rectangle.height; this.totalArea += area; console.log(` ⬜ Rectangle area: ${area.toFixed(2)}`); }
visitTriangle(triangle: Triangle): void { const s = (triangle.a + triangle.b + triangle.c) / 2; const area = Math.sqrt(s * (s-triangle.a) * (s-triangle.b) * (s-triangle.c)); this.totalArea += area; console.log(` 🔺 Triangle area: ${area.toFixed(2)}`); }
getTotal(): number { return this.totalArea; }}
class SVGExporter implements ShapeVisitor { private elements: string[] = [];
visitCircle(circle: Circle): void { this.elements.push(`<circle cx="50" cy="50" r="${circle.radius}" fill="blue"/>`); console.log(" ⭕ Exported circle to SVG"); }
visitRectangle(rectangle: Rectangle): void { this.elements.push(`<rect x="10" y="10" width="${rectangle.width}" height="${rectangle.height}" fill="green"/>`); console.log(" ⬜ Exported rectangle to SVG"); }
visitTriangle(triangle: Triangle): void { this.elements.push('<polygon points="50,10 10,90 90,90" fill="red"/>'); console.log(" 🔺 Exported triangle to SVG"); }
getSvg(): string { return `<svg xmlns="http://www.w3.org/2000/svg">\n ${this.elements.join("\n ")}\n</svg>`; }}
// Usageconst shapes: Shape[] = [ new Circle(5), new Rectangle(4, 6), new Triangle(3, 4, 5)];
console.log("📐 Calculating Areas:");const areaCalc = new AreaCalculator();shapes.forEach(shape => shape.accept(areaCalc));console.log(`\n 📊 Total Area: ${areaCalc.getTotal().toFixed(2)}`);
console.log("\n🎨 Exporting to SVG:");const svgExporter = new SVGExporter();shapes.forEach(shape => shape.accept(svgExporter));console.log("\n✅ Visitor Pattern: New operations without modifying shapes!");// Complete C++ implementation showing key structure
#include <iostream>#include <vector>#include <memory>#include <cmath>#include <string>
// Forward declarationsclass Circle;class Rectangle;class Triangle;
// Step 1: Define the Visitor interfaceclass ShapeVisitor {public: virtual ~ShapeVisitor() = default; virtual void visitCircle(Circle* circle) = 0; virtual void visitRectangle(Rectangle* rectangle) = 0; virtual void visitTriangle(Triangle* triangle) = 0;};
// Step 2: Define the Element interfaceclass Shape {public: virtual ~Shape() = default; virtual void accept(ShapeVisitor* visitor) = 0;};
// Step 3: Create Concrete Elementsclass Circle : public Shape {public: double radius;
Circle(double r) : radius(r) {}
void accept(ShapeVisitor* visitor) override { visitor->visitCircle(this); }};
class Rectangle : public Shape {public: double width, height;
Rectangle(double w, double h) : width(w), height(h) {}
void accept(ShapeVisitor* visitor) override { visitor->visitRectangle(this); }};
class Triangle : public Shape {public: double a, b, c;
Triangle(double a, double b, double c) : a(a), b(b), c(c) {}
void accept(ShapeVisitor* visitor) override { visitor->visitTriangle(this); }};
// Step 4: Create Concrete Visitorsclass AreaCalculator : public ShapeVisitor {private: double totalArea = 0.0;
public: void visitCircle(Circle* circle) override { double area = M_PI * circle->radius * circle->radius; totalArea += area; std::cout << " ⭕ Circle area: " << area << std::endl; }
void visitRectangle(Rectangle* rectangle) override { double area = rectangle->width * rectangle->height; totalArea += area; std::cout << " ⬜ Rectangle area: " << area << std::endl; }
void visitTriangle(Triangle* triangle) override { double s = (triangle->a + triangle->b + triangle->c) / 2; double area = std::sqrt(s * (s-triangle->a) * (s-triangle->b) * (s-triangle->c)); totalArea += area; std::cout << " 🔺 Triangle area: " << area << std::endl; }
double getTotal() const { return totalArea; }};
class SVGExporter : public ShapeVisitor {private: std::vector<std::string> elements;
public: void visitCircle(Circle* circle) override { elements.push_back("<circle cx=\"50\" cy=\"50\" r=\"" + std::to_string(circle->radius) + "\" fill=\"blue\"/>"); std::cout << " ⭕ Exported circle to SVG" << std::endl; }
void visitRectangle(Rectangle* rectangle) override { elements.push_back("<rect x=\"10\" y=\"10\" width=\"" + std::to_string(rectangle->width) + "\" height=\"" + std::to_string(rectangle->height) + "\" fill=\"green\"/>"); std::cout << " ⬜ Exported rectangle to SVG" << std::endl; }
void visitTriangle(Triangle* triangle) override { elements.push_back("<polygon points=\"50,10 10,90 90,90\" fill=\"red\"/>"); std::cout << " 🔺 Exported triangle to SVG" << std::endl; }
std::string getSvg() const { std::string svg = "<svg xmlns=\"http://www.w3.org/2000/svg\">"; for (const auto& elem : elements) { svg += "\n " + elem; } svg += "\n</svg>"; return svg; }};
// Usageint main() { std::vector<std::unique_ptr<Shape>> shapes; shapes.push_back(std::make_unique<Circle>(5)); shapes.push_back(std::make_unique<Rectangle>(4, 6)); shapes.push_back(std::make_unique<Triangle>(3, 4, 5));
std::cout << "📐 Calculating Areas:" << std::endl; AreaCalculator areaCalc; for (auto& shape : shapes) { shape->accept(&areaCalc); } std::cout << "\n 📊 Total Area: " << areaCalc.getTotal() << std::endl;
std::cout << "\n🎨 Exporting to SVG:" << std::endl; SVGExporter svgExporter; for (auto& shape : shapes) { shape->accept(&svgExporter); }
std::cout << "\n✅ Visitor Pattern: New operations without modifying shapes!" << std::endl; return 0;}// Complete C# implementation showing key structure
using System;using System.Collections.Generic;
// Step 1: Define the Visitor interfacepublic interface IShapeVisitor{ void VisitCircle(Circle circle); void VisitRectangle(Rectangle rectangle); void VisitTriangle(Triangle triangle);}
// Step 2: Define the Element interfacepublic interface IShape{ void Accept(IShapeVisitor visitor);}
// Step 3: Create Concrete Elementspublic class Circle : IShape{ public double Radius { get; set; }
public Circle(double radius) { Radius = radius; }
public void Accept(IShapeVisitor visitor) { visitor.VisitCircle(this); }}
public class Rectangle : IShape{ public double Width { get; set; } public double Height { get; set; }
public Rectangle(double width, double height) { Width = width; Height = height; }
public void Accept(IShapeVisitor visitor) { visitor.VisitRectangle(this); }}
public class Triangle : IShape{ public double A { get; set; } public double B { get; set; } public double C { get; set; }
public Triangle(double a, double b, double c) { A = a; B = b; C = c; }
public void Accept(IShapeVisitor visitor) { visitor.VisitTriangle(this); }}
// Step 4: Create Concrete Visitorspublic class AreaCalculator : IShapeVisitor{ private double totalArea = 0.0;
public void VisitCircle(Circle circle) { double area = Math.PI * circle.Radius * circle.Radius; totalArea += area; Console.WriteLine($" ⭕ Circle area: {area:F2}"); }
public void VisitRectangle(Rectangle rectangle) { double area = rectangle.Width * rectangle.Height; totalArea += area; Console.WriteLine($" ⬜ Rectangle area: {area:F2}"); }
public void VisitTriangle(Triangle triangle) { double s = (triangle.A + triangle.B + triangle.C) / 2; double area = Math.Sqrt(s * (s-triangle.A) * (s-triangle.B) * (s-triangle.C)); totalArea += area; Console.WriteLine($" 🔺 Triangle area: {area:F2}"); }
public double GetTotal() => totalArea;}
public class SVGExporter : IShapeVisitor{ private List<string> elements = new List<string>();
public void VisitCircle(Circle circle) { elements.Add($"<circle cx=\"50\" cy=\"50\" r=\"{circle.Radius}\" fill=\"blue\"/>"); Console.WriteLine(" ⭕ Exported circle to SVG"); }
public void VisitRectangle(Rectangle rectangle) { elements.Add($"<rect x=\"10\" y=\"10\" width=\"{rectangle.Width}\" height=\"{rectangle.Height}\" fill=\"green\"/>"); Console.WriteLine(" ⬜ Exported rectangle to SVG"); }
public void VisitTriangle(Triangle triangle) { elements.Add("<polygon points=\"50,10 10,90 90,90\" fill=\"red\"/>"); Console.WriteLine(" 🔺 Exported triangle to SVG"); }
public string GetSvg() { return $"<svg xmlns=\"http://www.w3.org/2000/svg\">\n {string.Join("\n ", elements)}\n</svg>"; }}
// Usageclass Program{ static void Main() { List<IShape> shapes = new List<IShape> { new Circle(5), new Rectangle(4, 6), new Triangle(3, 4, 5) };
Console.WriteLine("📐 Calculating Areas:"); AreaCalculator areaCalc = new AreaCalculator(); foreach (var shape in shapes) { shape.Accept(areaCalc); } Console.WriteLine($"\n 📊 Total Area: {areaCalc.GetTotal():F2}");
Console.WriteLine("\n🎨 Exporting to SVG:"); SVGExporter svgExporter = new SVGExporter(); foreach (var shape in shapes) { shape.Accept(svgExporter); }
Console.WriteLine("\n✅ Visitor Pattern: New operations without modifying shapes!"); }}package main
import ( "fmt" "math" "strings")
// Step 1: Visitor interface - one method per element typetype ShapeVisitor interface { VisitCircle(*Circle) VisitRectangle(*Rectangle) VisitTriangle(*Triangle)}
// Step 2: Element interfacetype Shape interface { Accept(ShapeVisitor)}
// Step 3: Concrete elementstype Circle struct{ Radius float64 }func (c *Circle) Accept(v ShapeVisitor) { v.VisitCircle(c) }
type Rectangle struct{ Width, Height float64 }func (r *Rectangle) Accept(v ShapeVisitor) { v.VisitRectangle(r) }
type Triangle struct{ A, B, C float64 }func (t *Triangle) Accept(v ShapeVisitor) { v.VisitTriangle(t) }
// Step 4: Concrete visitorstype AreaCalculator struct{ TotalArea float64 }
func (a *AreaCalculator) VisitCircle(c *Circle) { area := math.Pi * c.Radius * c.Radius a.TotalArea += area fmt.Printf(" ⭕ Circle area: %.2f\n", area)}func (a *AreaCalculator) VisitRectangle(r *Rectangle) { area := r.Width * r.Height a.TotalArea += area fmt.Printf(" ⬜ Rectangle area: %.2f\n", area)}func (a *AreaCalculator) VisitTriangle(t *Triangle) { s := (t.A + t.B + t.C) / 2 area := math.Sqrt(s * (s - t.A) * (s - t.B) * (s - t.C)) a.TotalArea += area fmt.Printf(" 🔺 Triangle area: %.2f\n", area)}
type SVGExporter struct{ elements []string }
func (s *SVGExporter) VisitCircle(c *Circle) { s.elements = append(s.elements, fmt.Sprintf(`<circle r="%.1f"/>`, c.Radius))}func (s *SVGExporter) VisitRectangle(r *Rectangle) { s.elements = append(s.elements, fmt.Sprintf(`<rect width="%.1f" height="%.1f"/>`, r.Width, r.Height))}func (s *SVGExporter) VisitTriangle(t *Triangle) { s.elements = append(s.elements, `<polygon points="50,10 10,90 90,90"/>`)}func (s *SVGExporter) GetSVG() string { return "<svg>\n " + strings.Join(s.elements, "\n ") + "\n</svg>"}
func main() { shapes := []Shape{&Circle{5}, &Rectangle{4, 6}, &Triangle{3, 4, 5}}
fmt.Println("📐 Calculating Areas:") calc := &AreaCalculator{} for _, s := range shapes { s.Accept(calc) } fmt.Printf("\n 📊 Total Area: %.2f\n", calc.TotalArea)
fmt.Println("\n✅ Visitor Pattern: New operations without modifying shapes!")}// Class Structuretrait ShapeVisitor { fn visit_circle(&mut self, radius: f64); fn visit_rectangle(&mut self, width: f64, height: f64);}trait Shape { fn accept(&self, visitor: &mut dyn ShapeVisitor);}struct Circle { radius: f64,}impl Shape for Circle { fn accept(&self, visitor: &mut dyn ShapeVisitor) { visitor.visit_circle(self.radius); }}Real-World Software Example: Document Exporter
Section titled “Real-World Software Example: Document Exporter”Now let’s see a realistic software example - a document system that needs to export to multiple formats.
The Problem
Section titled “The Problem”You’re building a document editor with different elements (headings, paragraphs, code blocks, images). You need to export to HTML, Markdown, PDF, and plain text. Without Visitor Pattern:
# ❌ Without Visitor Pattern - Export methods in every element!
class Heading: def __init__(self, text: str, level: int): self.text = text self.level = level
def to_html(self) -> str: return f"<h{self.level}>{self.text}</h{self.level}>"
def to_markdown(self) -> str: return f"{'#' * self.level} {self.text}"
def to_plain_text(self) -> str: return self.text.upper()
# New format? Add method here AND in every other class!
class Paragraph: def __init__(self, text: str): self.text = text
def to_html(self) -> str: return f"<p>{self.text}</p>"
def to_markdown(self) -> str: return self.text
def to_plain_text(self) -> str: return self.text
# New format? Add method here too!
class CodeBlock: def __init__(self, code: str, language: str): self.code = code self.language = language
def to_html(self) -> str: return f"<pre><code class='{self.language}'>{self.code}</code></pre>"
def to_markdown(self) -> str: return f"```{self.language}\n{self.code}\n```"
def to_plain_text(self) -> str: return self.code
# And here... for EVERY element type!
# Problems:# - 4 export formats × 5 element types = 20 methods!# - Adding PDF export = add method to ALL classes# - Related export code (all HTML) spread everywhere// ❌ Without Visitor Pattern - Export methods in every element!
class Heading { private String text; private int level;
public String toHtml() { return "<h" + level + ">" + text + "</h" + level + ">"; }
public String toMarkdown() { return "#".repeat(level) + " " + text; }
public String toPlainText() { return text.toUpperCase(); }
// New format? Add method here AND in every other class!}
class Paragraph { private String text;
public String toHtml() { return "<p>" + text + "</p>"; }
public String toMarkdown() { return text; }
public String toPlainText() { return text; }
// New format? Add method here too!}
// Problems:// - 4 export formats × 5 element types = 20 methods!// - Adding PDF export = add method to ALL classes// - Related export code (all HTML) spread everywhere// ❌ Without Visitor Pattern - Export methods in every element!
class Heading { constructor(private text: string, private level: number) {}
toHtml(): string { return `<h${this.level}>${this.text}</h${this.level}>`; }
toMarkdown(): string { return "#".repeat(this.level) + " " + this.text; }
toPlainText(): string { return this.text.toUpperCase(); }
// New format? Add method here AND in every other class!}
class Paragraph { constructor(private text: string) {}
toHtml(): string { return `<p>${this.text}</p>`; }
toMarkdown(): string { return this.text; }
toPlainText(): string { return this.text; }
// New format? Add method here too!}
class CodeBlock { constructor(private code: string, private language: string) {}
toHtml(): string { return `<pre><code class='${this.language}'>${this.code}</code></pre>`; }
toMarkdown(): string { return `\`\`\`${this.language}\n${this.code}\n\`\`\``; }
toPlainText(): string { return this.code; }
// And here... for EVERY element type!}
// Problems:// - 4 export formats × 5 element types = 20 methods!// - Adding PDF export = add method to ALL classes// - Related export code (all HTML) spread everywhere// ❌ Without Visitor Pattern - Export methods in every element!
#include <iostream>#include <string>
class Heading {private: std::string text; int level;
public: Heading(const std::string& text, int level) : text(text), level(level) {}
std::string toHtml() { return "<h" + std::to_string(level) + ">" + text + "</h" + std::to_string(level) + ">"; }
std::string toMarkdown() { return std::string(level, '#') + " " + text; }
std::string toPlainText() { std::string upper = text; for (auto& c : upper) c = std::toupper(c); return upper; }
// New format? Add method here AND in every other class!};
class Paragraph {private: std::string text;
public: Paragraph(const std::string& text) : text(text) {}
std::string toHtml() { return "<p>" + text + "</p>"; }
std::string toMarkdown() { return text; }
std::string toPlainText() { return text; }
// New format? Add method here too!};
class CodeBlock {private: std::string code; std::string language;
public: CodeBlock(const std::string& code, const std::string& language) : code(code), language(language) {}
std::string toHtml() { return "<pre><code class='" + language + "'>" + code + "</code></pre>"; }
std::string toMarkdown() { return "```" + language + "\n" + code + "\n```"; }
std::string toPlainText() { return code; }
// And here... for EVERY element type!};
// Problems:// - 4 export formats × 5 element types = 20 methods!// - Adding PDF export = add method to ALL classes// - Related export code (all HTML) spread everywhere// ❌ Without Visitor Pattern - Export methods in every element!
using System;
class Heading{ private string text; private int level;
public Heading(string text, int level) { this.text = text; this.level = level; }
public string ToHtml() { return $"<h{level}>{text}</h{level}>"; }
public string ToMarkdown() { return new string('#', level) + " " + text; }
public string ToPlainText() { return text.ToUpper(); }
// New format? Add method here AND in every other class!}
class Paragraph{ private string text;
public Paragraph(string text) { this.text = text; }
public string ToHtml() { return $"<p>{text}</p>"; }
public string ToMarkdown() { return text; }
public string ToPlainText() { return text; }
// New format? Add method here too!}
class CodeBlock{ private string code; private string language;
public CodeBlock(string code, string language) { this.code = code; this.language = language; }
public string ToHtml() { return $"<pre><code class='{language}'>{code}</code></pre>"; }
public string ToMarkdown() { return $"```{language}\n{code}\n```"; }
public string ToPlainText() { return code; }
// And here... for EVERY element type!}
// Problems:// - 4 export formats × 5 element types = 20 methods!// - Adding PDF export = add method to ALL classes// - Related export code (all HTML) spread everywherepackage main
import ( "fmt" "strings")
// ❌ Without Visitor Pattern - Export methods in every element!
type Heading struct { Text string Level int}
func (h *Heading) ToHTML() string { return fmt.Sprintf("<h%d>%s</h%d>", h.Level, h.Text, h.Level) }func (h *Heading) ToMarkdown() string { return strings.Repeat("#", h.Level) + " " + h.Text }// New format? Add method here AND in every other class!
type Paragraph struct{ Text string }
func (p *Paragraph) ToHTML() string { return "<p>" + p.Text + "</p>" }func (p *Paragraph) ToMarkdown() string { return p.Text }// And here too!
// Problems: N formats × M elements = N×M methods scattered everywhere!// The Problemenum Shape { Circle(f64), Rectangle(f64, f64),}impl Shape { fn area(&self) -> f64 { match self { Shape::Circle(r) => 3.14 * r * r, Shape::Rectangle(w, h) => w * h, } } fn export(&self) { println!("export shape"); }}Problems:
- Export methods scattered across all element classes
- Adding new format requires modifying ALL classes
- Related code (all HTML export) spread across files
- Elements have too many responsibilities
The Solution: Visitor Pattern
Section titled “The Solution: Visitor Pattern”Class Structure
Section titled “Class Structure”from abc import ABC, abstractmethodfrom typing import List
# Step 1: Define the Visitor interfaceclass DocumentVisitor(ABC): """Visitor interface for document operations"""
@abstractmethod def visit_heading(self, heading: 'Heading') -> None: pass
@abstractmethod def visit_paragraph(self, paragraph: 'Paragraph') -> None: pass
@abstractmethod def visit_code_block(self, code_block: 'CodeBlock') -> None: pass
@abstractmethod def visit_image(self, image: 'Image') -> None: pass
# Step 2: Define the Element interfaceclass DocumentElement(ABC): """Element interface - document elements accept visitors"""
@abstractmethod def accept(self, visitor: DocumentVisitor) -> None: pass
# Step 3: Create Concrete Elementsclass Heading(DocumentElement): """Heading element"""
def __init__(self, text: str, level: int = 1): self.text = text self.level = min(max(level, 1), 6) # H1-H6
def accept(self, visitor: DocumentVisitor) -> None: visitor.visit_heading(self)
class Paragraph(DocumentElement): """Paragraph element"""
def __init__(self, text: str): self.text = text
def accept(self, visitor: DocumentVisitor) -> None: visitor.visit_paragraph(self)
class CodeBlock(DocumentElement): """Code block element"""
def __init__(self, code: str, language: str = ""): self.code = code self.language = language
def accept(self, visitor: DocumentVisitor) -> None: visitor.visit_code_block(self)
class Image(DocumentElement): """Image element"""
def __init__(self, src: str, alt: str = ""): self.src = src self.alt = alt
def accept(self, visitor: DocumentVisitor) -> None: visitor.visit_image(self)
# Step 4: Create Concrete Visitorsclass HTMLExporter(DocumentVisitor): """Exports document to HTML"""
def __init__(self): self.output: List[str] = []
def visit_heading(self, heading: Heading) -> None: self.output.append( f"<h{heading.level}>{heading.text}</h{heading.level}>" )
def visit_paragraph(self, paragraph: Paragraph) -> None: self.output.append(f"<p>{paragraph.text}</p>")
def visit_code_block(self, code_block: CodeBlock) -> None: lang = f' class="language-{code_block.language}"' if code_block.language else '' self.output.append( f"<pre><code{lang}>{code_block.code}</code></pre>" )
def visit_image(self, image: Image) -> None: self.output.append( f'<img src="{image.src}" alt="{image.alt}"/>' )
def get_output(self) -> str: return "\n".join(self.output)
class MarkdownExporter(DocumentVisitor): """Exports document to Markdown"""
def __init__(self): self.output: List[str] = []
def visit_heading(self, heading: Heading) -> None: self.output.append(f"{'#' * heading.level} {heading.text}")
def visit_paragraph(self, paragraph: Paragraph) -> None: self.output.append(paragraph.text)
def visit_code_block(self, code_block: CodeBlock) -> None: self.output.append(f"```{code_block.language}") self.output.append(code_block.code) self.output.append("```")
def visit_image(self, image: Image) -> None: self.output.append(f"")
def get_output(self) -> str: return "\n\n".join(self.output)
class PlainTextExporter(DocumentVisitor): """Exports document to plain text"""
def __init__(self): self.output: List[str] = []
def visit_heading(self, heading: Heading) -> None: self.output.append(heading.text.upper()) self.output.append("=" * len(heading.text))
def visit_paragraph(self, paragraph: Paragraph) -> None: self.output.append(paragraph.text)
def visit_code_block(self, code_block: CodeBlock) -> None: self.output.append("--- CODE ---") self.output.append(code_block.code) self.output.append("--- END ---")
def visit_image(self, image: Image) -> None: self.output.append(f"[Image: {image.alt}]")
def get_output(self) -> str: return "\n\n".join(self.output)
class WordCounter(DocumentVisitor): """Counts words in the document"""
def __init__(self): self.word_count = 0 self.code_lines = 0 self.image_count = 0
def visit_heading(self, heading: Heading) -> None: self.word_count += len(heading.text.split())
def visit_paragraph(self, paragraph: Paragraph) -> None: self.word_count += len(paragraph.text.split())
def visit_code_block(self, code_block: CodeBlock) -> None: self.code_lines += len(code_block.code.split('\n'))
def visit_image(self, image: Image) -> None: self.image_count += 1
def get_stats(self) -> dict: return { "words": self.word_count, "code_lines": self.code_lines, "images": self.image_count }
# Step 5: Create a Document class to hold elementsclass Document: """Document containing multiple elements"""
def __init__(self, title: str): self.title = title self.elements: List[DocumentElement] = []
def add(self, element: DocumentElement) -> 'Document': self.elements.append(element) return self
def accept(self, visitor: DocumentVisitor) -> None: """Let visitor visit all elements""" for element in self.elements: element.accept(visitor)
# Step 6: Use the patterndef main(): print("=" * 60) print("Document Exporter (Visitor Pattern)") print("=" * 60)
# Create a document doc = Document("My Article") doc.add(Heading("Introduction to Design Patterns", 1)) doc.add(Paragraph("Design patterns are reusable solutions to common problems in software design.")) doc.add(Heading("Example Code", 2)) doc.add(CodeBlock("def hello():\n print('Hello, World!')", "python")) doc.add(Paragraph("The code above demonstrates a simple function.")) doc.add(Image("diagram.png", "Class Diagram"))
# Export to HTML print("\n📄 HTML Export:") print("-" * 40) html_exporter = HTMLExporter() doc.accept(html_exporter) print(html_exporter.get_output())
# Export to Markdown print("\n📝 Markdown Export:") print("-" * 40) md_exporter = MarkdownExporter() doc.accept(md_exporter) print(md_exporter.get_output())
# Export to Plain Text print("\n📃 Plain Text Export:") print("-" * 40) text_exporter = PlainTextExporter() doc.accept(text_exporter) print(text_exporter.get_output())
# Get document statistics print("\n📊 Document Statistics:") print("-" * 40) counter = WordCounter() doc.accept(counter) stats = counter.get_stats() print(f" Words: {stats['words']}") print(f" Code Lines: {stats['code_lines']}") print(f" Images: {stats['images']}")
print("\n" + "=" * 60) print("✅ Visitor Pattern: New export format = Just one new visitor!") print("✅ All HTML logic in HTMLExporter - easy to maintain!")
if __name__ == "__main__": main()import java.util.*;
// Step 1: Define the Visitor interfaceinterface DocumentVisitor { /** * Visitor interface for document operations */ void visitHeading(Heading heading); void visitParagraph(Paragraph paragraph); void visitCodeBlock(CodeBlock codeBlock); void visitImage(Image image);}
// Step 2: Define the Element interfaceinterface DocumentElement { /** * Element interface - document elements accept visitors */ void accept(DocumentVisitor visitor);}
// Step 3: Create Concrete Elementsclass Heading implements DocumentElement { private String text; private int level;
public Heading(String text, int level) { this.text = text; this.level = Math.min(Math.max(level, 1), 6); }
public String getText() { return text; } public int getLevel() { return level; }
@Override public void accept(DocumentVisitor visitor) { visitor.visitHeading(this); }}
class Paragraph implements DocumentElement { private String text;
public Paragraph(String text) { this.text = text; }
public String getText() { return text; }
@Override public void accept(DocumentVisitor visitor) { visitor.visitParagraph(this); }}
class CodeBlock implements DocumentElement { private String code; private String language;
public CodeBlock(String code, String language) { this.code = code; this.language = language; }
public String getCode() { return code; } public String getLanguage() { return language; }
@Override public void accept(DocumentVisitor visitor) { visitor.visitCodeBlock(this); }}
class Image implements DocumentElement { private String src; private String alt;
public Image(String src, String alt) { this.src = src; this.alt = alt; }
public String getSrc() { return src; } public String getAlt() { return alt; }
@Override public void accept(DocumentVisitor visitor) { visitor.visitImage(this); }}
// Step 4: Create Concrete Visitorsclass HTMLExporter implements DocumentVisitor { private List<String> output = new ArrayList<>();
@Override public void visitHeading(Heading heading) { output.add(String.format("<h%d>%s</h%d>", heading.getLevel(), heading.getText(), heading.getLevel())); }
@Override public void visitParagraph(Paragraph paragraph) { output.add(String.format("<p>%s</p>", paragraph.getText())); }
@Override public void visitCodeBlock(CodeBlock codeBlock) { String lang = codeBlock.getLanguage().isEmpty() ? "" : " class=\"language-" + codeBlock.getLanguage() + "\""; output.add(String.format("<pre><code%s>%s</code></pre>", lang, codeBlock.getCode())); }
@Override public void visitImage(Image image) { output.add(String.format("<img src=\"%s\" alt=\"%s\"/>", image.getSrc(), image.getAlt())); }
public String getOutput() { return String.join("\n", output); }}
class MarkdownExporter implements DocumentVisitor { private List<String> output = new ArrayList<>();
@Override public void visitHeading(Heading heading) { output.add("#".repeat(heading.getLevel()) + " " + heading.getText()); }
@Override public void visitParagraph(Paragraph paragraph) { output.add(paragraph.getText()); }
@Override public void visitCodeBlock(CodeBlock codeBlock) { output.add("```" + codeBlock.getLanguage()); output.add(codeBlock.getCode()); output.add("```"); }
@Override public void visitImage(Image image) { output.add(String.format("", image.getAlt(), image.getSrc())); }
public String getOutput() { return String.join("\n\n", output); }}
class WordCounter implements DocumentVisitor { private int wordCount = 0; private int codeLines = 0; private int imageCount = 0;
@Override public void visitHeading(Heading heading) { wordCount += heading.getText().split("\\s+").length; }
@Override public void visitParagraph(Paragraph paragraph) { wordCount += paragraph.getText().split("\\s+").length; }
@Override public void visitCodeBlock(CodeBlock codeBlock) { codeLines += codeBlock.getCode().split("\n").length; }
@Override public void visitImage(Image image) { imageCount++; }
public Map<String, Integer> getStats() { Map<String, Integer> stats = new HashMap<>(); stats.put("words", wordCount); stats.put("code_lines", codeLines); stats.put("images", imageCount); return stats; }}
// Step 5: Document classclass Document { private String title; private List<DocumentElement> elements = new ArrayList<>();
public Document(String title) { this.title = title; }
public Document add(DocumentElement element) { elements.add(element); return this; }
public void accept(DocumentVisitor visitor) { for (DocumentElement element : elements) { element.accept(visitor); } }}
// Step 6: Use the patternpublic class Main { public static void main(String[] args) { System.out.println("=".repeat(60)); System.out.println("Document Exporter (Visitor Pattern)"); System.out.println("=".repeat(60));
// Create a document Document doc = new Document("My Article"); doc.add(new Heading("Introduction to Design Patterns", 1)) .add(new Paragraph("Design patterns are reusable solutions.")) .add(new Heading("Example Code", 2)) .add(new CodeBlock("def hello():\n print('Hello!')", "python")) .add(new Image("diagram.png", "Class Diagram"));
// Export to HTML System.out.println("\n📄 HTML Export:"); System.out.println("-".repeat(40)); HTMLExporter htmlExporter = new HTMLExporter(); doc.accept(htmlExporter); System.out.println(htmlExporter.getOutput());
// Export to Markdown System.out.println("\n📝 Markdown Export:"); System.out.println("-".repeat(40)); MarkdownExporter mdExporter = new MarkdownExporter(); doc.accept(mdExporter); System.out.println(mdExporter.getOutput());
// Get statistics System.out.println("\n📊 Document Statistics:"); System.out.println("-".repeat(40)); WordCounter counter = new WordCounter(); doc.accept(counter); Map<String, Integer> stats = counter.getStats(); System.out.println(" Words: " + stats.get("words")); System.out.println(" Code Lines: " + stats.get("code_lines")); System.out.println(" Images: " + stats.get("images"));
System.out.println("\n✅ Visitor Pattern: New format = One new visitor!"); }}// Complete TypeScript implementation (streamlined but functional)
// Step 1: Define the Visitor interfaceinterface DocumentVisitor { visitHeading(heading: Heading): void; visitParagraph(paragraph: Paragraph): void; visitCodeBlock(codeBlock: CodeBlock): void;}
// Step 2: Define the Element interfaceinterface DocumentElement { accept(visitor: DocumentVisitor): void;}
// Step 3: Create Concrete Elementsclass Heading implements DocumentElement { constructor(public text: string, public level: number) { this.level = Math.min(Math.max(level, 1), 6); }
accept(visitor: DocumentVisitor): void { visitor.visitHeading(this); }}
class Paragraph implements DocumentElement { constructor(public text: string) {}
accept(visitor: DocumentVisitor): void { visitor.visitParagraph(this); }}
class CodeBlock implements DocumentElement { constructor(public code: string, public language: string) {}
accept(visitor: DocumentVisitor): void { visitor.visitCodeBlock(this); }}
// Step 4: Create Concrete Visitorsclass HTMLExporter implements DocumentVisitor { private output: string[] = [];
visitHeading(heading: Heading): void { this.output.push(`<h${heading.level}>${heading.text}</h${heading.level}>`); }
visitParagraph(paragraph: Paragraph): void { this.output.push(`<p>${paragraph.text}</p>`); }
visitCodeBlock(codeBlock: CodeBlock): void { this.output.push(`<pre><code class="${codeBlock.language}">${codeBlock.code}</code></pre>`); }
getOutput(): string { return this.output.join("\n"); }}
class MarkdownExporter implements DocumentVisitor { private output: string[] = [];
visitHeading(heading: Heading): void { this.output.push("#".repeat(heading.level) + " " + heading.text); }
visitParagraph(paragraph: Paragraph): void { this.output.push(paragraph.text); }
visitCodeBlock(codeBlock: CodeBlock): void { this.output.push(`\`\`\`${codeBlock.language}\n${codeBlock.code}\n\`\`\``); }
getOutput(): string { return this.output.join("\n\n"); }}
// Usageconst doc: DocumentElement[] = [ new Heading("Introduction", 1), new Paragraph("Visitor pattern separates operations from object structure."), new CodeBlock("shape.accept(visitor);", "typescript")];
console.log("📄 Exporting to HTML:");const htmlExporter = new HTMLExporter();doc.forEach(elem => elem.accept(htmlExporter));console.log(htmlExporter.getOutput());
console.log("\n📄 Exporting to Markdown:");const mdExporter = new MarkdownExporter();doc.forEach(elem => elem.accept(mdExporter));console.log(mdExporter.getOutput());
console.log("\n✅ Visitor Pattern: New format = One new visitor!");// Complete C++ implementation (streamlined but functional)
#include <iostream>#include <vector>#include <string>#include <memory>#include <algorithm>
// Forward declarationsclass Heading;class Paragraph;class CodeBlock;
// Step 1: Define the Visitor interfaceclass DocumentVisitor {public: virtual ~DocumentVisitor() = default; virtual void visitHeading(Heading* heading) = 0; virtual void visitParagraph(Paragraph* paragraph) = 0; virtual void visitCodeBlock(CodeBlock* codeBlock) = 0;};
// Step 2: Define the Element interfaceclass DocumentElement {public: virtual ~DocumentElement() = default; virtual void accept(DocumentVisitor* visitor) = 0;};
// Step 3: Create Concrete Elementsclass Heading : public DocumentElement {public: std::string text; int level;
Heading(const std::string& text, int level) : text(text), level(std::min(std::max(level, 1), 6)) {}
void accept(DocumentVisitor* visitor) override { visitor->visitHeading(this); }};
class Paragraph : public DocumentElement {public: std::string text;
Paragraph(const std::string& text) : text(text) {}
void accept(DocumentVisitor* visitor) override { visitor->visitParagraph(this); }};
class CodeBlock : public DocumentElement {public: std::string code; std::string language;
CodeBlock(const std::string& code, const std::string& language) : code(code), language(language) {}
void accept(DocumentVisitor* visitor) override { visitor->visitCodeBlock(this); }};
// Step 4: Create Concrete Visitorsclass HTMLExporter : public DocumentVisitor {private: std::vector<std::string> output;
public: void visitHeading(Heading* heading) override { output.push_back("<h" + std::to_string(heading->level) + ">" + heading->text + "</h" + std::to_string(heading->level) + ">"); }
void visitParagraph(Paragraph* paragraph) override { output.push_back("<p>" + paragraph->text + "</p>"); }
void visitCodeBlock(CodeBlock* codeBlock) override { output.push_back("<pre><code class=\"" + codeBlock->language + "\">" + codeBlock->code + "</code></pre>"); }
std::string getOutput() const { std::string result; for (const auto& line : output) { result += line + "\n"; } return result; }};
class MarkdownExporter : public DocumentVisitor {private: std::vector<std::string> output;
public: void visitHeading(Heading* heading) override { output.push_back(std::string(heading->level, '#') + " " + heading->text); }
void visitParagraph(Paragraph* paragraph) override { output.push_back(paragraph->text); }
void visitCodeBlock(CodeBlock* codeBlock) override { output.push_back("```" + codeBlock->language + "\n" + codeBlock->code + "\n```"); }
std::string getOutput() const { std::string result; for (size_t i = 0; i < output.size(); ++i) { result += output[i]; if (i < output.size() - 1) result += "\n\n"; } return result; }};
// Usageint main() { std::vector<std::unique_ptr<DocumentElement>> doc; doc.push_back(std::make_unique<Heading>("Introduction", 1)); doc.push_back(std::make_unique<Paragraph>("Visitor pattern separates operations from object structure.")); doc.push_back(std::make_unique<CodeBlock>("shape.accept(visitor);", "cpp"));
std::cout << "📄 Exporting to HTML:" << std::endl; HTMLExporter htmlExporter; for (auto& elem : doc) { elem->accept(&htmlExporter); } std::cout << htmlExporter.getOutput() << std::endl;
std::cout << "📄 Exporting to Markdown:" << std::endl; MarkdownExporter mdExporter; for (auto& elem : doc) { elem->accept(&mdExporter); } std::cout << mdExporter.getOutput() << std::endl;
std::cout << "\n✅ Visitor Pattern: New format = One new visitor!" << std::endl; return 0;}// Complete C# implementation (streamlined but functional)
using System;using System.Collections.Generic;
// Step 1: Define the Visitor interfacepublic interface IDocumentVisitor{ void VisitHeading(Heading heading); void VisitParagraph(Paragraph paragraph); void VisitCodeBlock(CodeBlock codeBlock);}
// Step 2: Define the Element interfacepublic interface IDocumentElement{ void Accept(IDocumentVisitor visitor);}
// Step 3: Create Concrete Elementspublic class Heading : IDocumentElement{ public string Text { get; set; } public int Level { get; set; }
public Heading(string text, int level) { Text = text; Level = Math.Min(Math.Max(level, 1), 6); }
public void Accept(IDocumentVisitor visitor) { visitor.VisitHeading(this); }}
public class Paragraph : IDocumentElement{ public string Text { get; set; }
public Paragraph(string text) { Text = text; }
public void Accept(IDocumentVisitor visitor) { visitor.VisitParagraph(this); }}
public class CodeBlock : IDocumentElement{ public string Code { get; set; } public string Language { get; set; }
public CodeBlock(string code, string language) { Code = code; Language = language; }
public void Accept(IDocumentVisitor visitor) { visitor.VisitCodeBlock(this); }}
// Step 4: Create Concrete Visitorspublic class HTMLExporter : IDocumentVisitor{ private List<string> output = new List<string>();
public void VisitHeading(Heading heading) { output.Add($"<h{heading.Level}>{heading.Text}</h{heading.Level}>"); }
public void VisitParagraph(Paragraph paragraph) { output.Add($"<p>{paragraph.Text}</p>"); }
public void VisitCodeBlock(CodeBlock codeBlock) { output.Add($"<pre><code class=\"{codeBlock.Language}\">{codeBlock.Code}</code></pre>"); }
public string GetOutput() { return string.Join("\n", output); }}
public class MarkdownExporter : IDocumentVisitor{ private List<string> output = new List<string>();
public void VisitHeading(Heading heading) { output.Add(new string('#', heading.Level) + " " + heading.Text); }
public void VisitParagraph(Paragraph paragraph) { output.Add(paragraph.Text); }
public void VisitCodeBlock(CodeBlock codeBlock) { output.Add($"```{codeBlock.Language}\n{codeBlock.Code}\n```"); }
public string GetOutput() { return string.Join("\n\n", output); }}
// Usageclass Program{ static void Main() { List<IDocumentElement> doc = new List<IDocumentElement> { new Heading("Introduction", 1), new Paragraph("Visitor pattern separates operations from object structure."), new CodeBlock("shape.Accept(visitor);", "csharp") };
Console.WriteLine("📄 Exporting to HTML:"); HTMLExporter htmlExporter = new HTMLExporter(); foreach (var elem in doc) { elem.Accept(htmlExporter); } Console.WriteLine(htmlExporter.GetOutput());
Console.WriteLine("\n📄 Exporting to Markdown:"); MarkdownExporter mdExporter = new MarkdownExporter(); foreach (var elem in doc) { elem.Accept(mdExporter); } Console.WriteLine(mdExporter.GetOutput());
Console.WriteLine("\n✅ Visitor Pattern: New format = One new visitor!"); }}package main
import ( "fmt" "strings")
// Visitor interfacetype DocumentVisitor interface { VisitHeading(*Heading) VisitParagraph(*Paragraph) VisitCodeBlock(*CodeBlock)}
// Element interfacetype DocumentElement interface { Accept(DocumentVisitor)}
// Concrete elementstype Heading struct{ Text string; Level int }func (h *Heading) Accept(v DocumentVisitor) { v.VisitHeading(h) }
type Paragraph struct{ Text string }func (p *Paragraph) Accept(v DocumentVisitor) { v.VisitParagraph(p) }
type CodeBlock struct{ Code, Language string }func (c *CodeBlock) Accept(v DocumentVisitor) { v.VisitCodeBlock(c) }
// HTML Visitortype HTMLExporter struct{ output []string }
func (e *HTMLExporter) VisitHeading(h *Heading) { e.output = append(e.output, fmt.Sprintf("<h%d>%s</h%d>", h.Level, h.Text, h.Level))}func (e *HTMLExporter) VisitParagraph(p *Paragraph) { e.output = append(e.output, "<p>"+p.Text+"</p>")}func (e *HTMLExporter) VisitCodeBlock(c *CodeBlock) { e.output = append(e.output, fmt.Sprintf("<pre><code class=%q>%s</code></pre>", c.Language, c.Code))}func (e *HTMLExporter) GetOutput() string { return strings.Join(e.output, "\n") }
// Markdown Visitortype MarkdownExporter struct{ output []string }
func (e *MarkdownExporter) VisitHeading(h *Heading) { e.output = append(e.output, strings.Repeat("#", h.Level)+" "+h.Text)}func (e *MarkdownExporter) VisitParagraph(p *Paragraph) { e.output = append(e.output, p.Text) }func (e *MarkdownExporter) VisitCodeBlock(c *CodeBlock) { e.output = append(e.output, fmt.Sprintf("```%s\n%s\n```", c.Language, c.Code))}func (e *MarkdownExporter) GetOutput() string { return strings.Join(e.output, "\n\n") }
func main() { doc := []DocumentElement{ &Heading{"Introduction", 1}, &Paragraph{"Visitor pattern separates operations from object structure."}, &CodeBlock{"shape.Accept(visitor)", "go"}, }
fmt.Println("📄 Exporting to HTML:") html := &HTMLExporter{} for _, elem := range doc { elem.Accept(html) } fmt.Println(html.GetOutput())
fmt.Println("\n✅ Visitor Pattern: New format = One new visitor!")}// Class Structuretrait ShapeVisitor { fn visit_circle(&mut self, radius: f64); fn visit_rectangle(&mut self, width: f64, height: f64);}trait Shape { fn accept(&self, visitor: &mut dyn ShapeVisitor);}struct Circle { radius: f64,}impl Shape for Circle { fn accept(&self, visitor: &mut dyn ShapeVisitor) { visitor.visit_circle(self.radius); }}Visitor Pattern Concepts
Section titled “Visitor Pattern Concepts”Double Dispatch
Section titled “Double Dispatch”Visitor Pattern uses double dispatch to determine which code to run:
- First dispatch: Based on element type (Circle, Rectangle)
- Second dispatch: Based on visitor type (AreaCalculator, SVGExporter)
This allows choosing behavior based on BOTH types at runtime!
When to Use vs When Not
Section titled “When to Use vs When Not”Common Mistakes to Avoid
Section titled “Common Mistakes to Avoid”Mistake 1: Visitor Knows Too Much About Elements
Section titled “Mistake 1: Visitor Knows Too Much About Elements”# ❌ Bad: Visitor accesses private internalsclass BadVisitor: def visit_circle(self, circle): # Bad: Accessing private attributes! area = 3.14 * circle._internal_radius ** 2
# ✅ Good: Use public gettersclass GoodVisitor: def visit_circle(self, circle): # Good: Uses public interface area = 3.14 * circle.get_radius() ** 2// ❌ Bad: Visitor accesses private fieldsclass BadVisitor implements ShapeVisitor { @Override public void visitCircle(Circle circle) { // Bad: Would need reflection or package access double area = Math.PI * circle.radius * circle.radius; }}
// ✅ Good: Use public gettersclass GoodVisitor implements ShapeVisitor { @Override public void visitCircle(Circle circle) { // Good: Uses public interface double area = Math.PI * circle.getRadius() * circle.getRadius(); }}// ❌ Bad: Visitor accesses private internalsclass BadVisitor { visitCircle(circle: any): void { // Bad: Accessing private properties! const area = 3.14 * circle._internalRadius ** 2; }}
// ✅ Good: Use public gettersclass GoodVisitor { visitCircle(circle: { getRadius(): number }): void { // Good: Uses public interface const area = 3.14 * circle.getRadius() ** 2; }}// ❌ Bad: Visitor accesses private membersclass BadVisitor : public ShapeVisitor {public: void visitCircle(Circle* circle) override { // Bad: Accessing private member (won't compile) // double area = M_PI * circle->radius * circle->radius; }};
// ✅ Good: Use public gettersclass GoodVisitor : public ShapeVisitor {public: void visitCircle(Circle* circle) override { // Good: Uses public interface double area = M_PI * circle->getRadius() * circle->getRadius(); }};// ❌ Bad: Visitor accesses private fieldsclass BadVisitor : IShapeVisitor{ public void VisitCircle(Circle circle) { // Bad: Would need reflection or internal access // double area = Math.PI * circle.radius * circle.radius; }}
// ✅ Good: Use public propertiesclass GoodVisitor : IShapeVisitor{ public void VisitCircle(Circle circle) { // Good: Uses public interface double area = Math.PI * circle.GetRadius() * circle.GetRadius(); }}// ❌ Bad: Visitor accessing unexported fields (would be a compile error in Go)// Go already enforces this via unexported fields!
// ✅ Good: Use exported fields or getter methodstype CircleGood struct{ Radius float64 } // exported
type GoodAreaVisitor struct{}
func (v *GoodAreaVisitor) VisitCircle(c *CircleGood) { area := math.Pi * c.Radius * c.Radius // Good: uses public field fmt.Printf("Area: %.2f\n", area)}// Mistake 1: Visitor Knows Too Much About Elementsenum Shape { Circle(f64), Rectangle(f64, f64),}impl Shape { fn area(&self) -> f64 { match self { Shape::Circle(r) => 3.14 * r * r, Shape::Rectangle(w, h) => w * h, } } fn export(&self) { println!("export shape"); }}Mistake 2: Forgetting to Add Visit Methods for New Elements
Section titled “Mistake 2: Forgetting to Add Visit Methods for New Elements”# ❌ Bad: Adding new element without updating visitorsclass Pentagon(Shape): def accept(self, visitor): visitor.visit_pentagon(self) # But visitors don't have this!
# ✅ Good: Use default implementation or abstract baseclass ShapeVisitor(ABC): @abstractmethod def visit_circle(self, circle): pass
@abstractmethod def visit_rectangle(self, rect): pass
@abstractmethod def visit_pentagon(self, pentagon): pass # Add when adding Pentagon!// ❌ Bad: Adding new element without updating interfaceclass Pentagon implements Shape { @Override public void accept(ShapeVisitor visitor) { visitor.visitPentagon(this); // Compile error! }}
// ✅ Good: Update visitor interface when adding elementsinterface ShapeVisitor { void visitCircle(Circle circle); void visitRectangle(Rectangle rectangle); void visitPentagon(Pentagon pentagon); // Add when adding Pentagon!}// ❌ Bad: Adding new element without updating visitorsclass Pentagon implements Shape { accept(visitor: ShapeVisitor): void { (visitor as any).visitPentagon(this); // But visitors don't have this! }}
// ✅ Good: Update visitor interface when adding elementsinterface ShapeVisitor { visitCircle(circle: Circle): void; visitRectangle(rectangle: Rectangle): void; visitPentagon(pentagon: Pentagon): void; // Add when adding Pentagon!}// ❌ Bad: Adding new element without updating interfaceclass Pentagon : public Shape {public: void accept(ShapeVisitor* visitor) override { // Won't compile! visitPentagon doesn't exist // visitor->visitPentagon(this); }};
// ✅ Good: Update visitor interface when adding elementsclass ShapeVisitor {public: virtual ~ShapeVisitor() = default; virtual void visitCircle(Circle* circle) = 0; virtual void visitRectangle(Rectangle* rectangle) = 0; virtual void visitPentagon(Pentagon* pentagon) = 0; // Add when adding Pentagon!};// ❌ Bad: Adding new element without updating interfaceclass Pentagon : IShape{ public void Accept(IShapeVisitor visitor) { // Won't compile! VisitPentagon doesn't exist // ((dynamic)visitor).VisitPentagon(this); }}
// ✅ Good: Update visitor interface when adding elementspublic interface IShapeVisitor{ void VisitCircle(Circle circle); void VisitRectangle(Rectangle rectangle); void VisitPentagon(Pentagon pentagon); // Add when adding Pentagon!}// ❌ Bad: Adding new element without updating visitor interface// In Go, if you add VisitPentagon to the interface, all implementations// must implement it - the compiler enforces completeness!
// ✅ Good: Update visitor interface when adding elementstype ShapeVisitorFull interface { VisitCircle(*Circle) VisitRectangle(*Rectangle) VisitPentagon(*Pentagon) // Add when adding Pentagon!}// Go's type system guarantees all visitors implement all methods.// Mistake 2: Forgetting to Add Visit Methods for New Elementsenum Shape { Circle(f64), Rectangle(f64, f64),}impl Shape { fn area(&self) -> f64 { match self { Shape::Circle(r) => 3.14 * r * r, Shape::Rectangle(w, h) => w * h, } } fn export(&self) { println!("export shape"); }}Mistake 3: Using Visitor When Structure Changes Often
Section titled “Mistake 3: Using Visitor When Structure Changes Often”# ❌ Bad: Using visitor when elements change frequently# If you add Triangle, Square, Pentagon, Hexagon...# Every visitor must be updated!
# ✅ Better: Use polymorphism when structure changes oftenclass Shape(ABC): @abstractmethod def area(self) -> float: pass
@abstractmethod def perimeter(self) -> float: pass
# Each shape implements its own operations# Adding new shape = just one new class// ❌ Bad: Using visitor when elements change frequently// If you add Triangle, Square, Pentagon, Hexagon...// Every visitor must be updated!
// ✅ Better: Use polymorphism when structure changes ofteninterface Shape { double area(); double perimeter();}
// Each shape implements its own operations// Adding new shape = just one new class// ❌ Bad: Using visitor when elements change frequently// If you add Triangle, Square, Pentagon, Hexagon...// Every visitor must be updated!
// ✅ Better: Use polymorphism when structure changes ofteninterface Shape { area(): number; perimeter(): number;}
// Each shape implements its own operations// Adding new shape = just one new class// ❌ Bad: Using visitor when elements change frequently// If you add Triangle, Square, Pentagon, Hexagon...// Every visitor must be updated!
// ✅ Better: Use polymorphism when structure changes oftenclass Shape {public: virtual ~Shape() = default; virtual double area() = 0; virtual double perimeter() = 0;};
// Each shape implements its own operations// Adding new shape = just one new class// ❌ Bad: Using visitor when elements change frequently// If you add Triangle, Square, Pentagon, Hexagon...// Every visitor must be updated!
// ✅ Better: Use polymorphism when structure changes oftenpublic interface IShape{ double Area(); double Perimeter();}
// Each shape implements its own operations// Adding new shape = just one new class// ❌ Bad: Using visitor when elements change frequently// If you add Triangle, Square, Pentagon, Hexagon...// Every visitor must be updated!
// ✅ Better: Use interfaces directly when structure changes oftentype Shape interface { Area() float64 Perimeter() float64}// Each shape implements its own operations.// Adding new shape = just one new struct + implement interface.// Mistake 3: Using Visitor When Structure Changes Oftenenum Shape { Circle(f64), Rectangle(f64, f64),}impl Shape { fn area(&self) -> f64 { match self { Shape::Circle(r) => 3.14 * r * r, Shape::Rectangle(w, h) => w * h, } } fn export(&self) { println!("export shape"); }}Benefits of Visitor Pattern
Section titled “Benefits of Visitor Pattern”- Open/Closed for Operations - Add operations without modifying elements
- Single Responsibility - Each visitor has one operation
- Related Code Together - All export logic in one class
- Accumulate State - Visitors can gather info across visits
- Double Dispatch - Operation based on both types
- Clean Elements - Elements don’t have operation logic
Revision: Quick Catch-Up
Section titled “Revision: Quick Catch-Up”What is Visitor Pattern?
Section titled “What is Visitor Pattern?”Visitor Pattern is a behavioral design pattern that lets you add new operations to objects without modifying them. It separates algorithms from the object structure they operate on.
Why Use It?
Section titled “Why Use It?”- ✅ Add operations - Without modifying element classes
- ✅ Single Responsibility - Each visitor = one operation
- ✅ Related code together - All HTML export in one class
- ✅ Accumulate state - Gather info across visits
- ✅ Double dispatch - Choose based on both types
How It Works?
Section titled “How It Works?”- Define Visitor interface - Visit method per element type
- Define Element interface - accept(visitor) method
- Create Concrete Elements - Call visitor.visit_X(this)
- Create Concrete Visitors - Implement operation per type
- Client - Creates visitor, passes to elements
Key Components
Section titled “Key Components”Element.accept(visitor) → visitor.visit_X(element)- Visitor - Interface with visit methods
- Concrete Visitor - Implements specific operation
- Element - Interface with accept method
- Concrete Element - Calls appropriate visit method
Simple Example
Section titled “Simple Example”from abc import ABC, abstractmethod
class ShapeVisitor(ABC): @abstractmethod def visit_circle(self, circle): pass @abstractmethod def visit_rectangle(self, rect): pass
class Shape(ABC): @abstractmethod def accept(self, visitor): pass
class Circle(Shape): def accept(self, visitor): visitor.visit_circle(self) # Double dispatch!
class AreaCalculator(ShapeVisitor): def visit_circle(self, circle): return 3.14 * circle.radius ** 2interface ShapeVisitor { double visitCircle(Circle circle); double visitRectangle(Rectangle rect);}
interface Shape { double accept(ShapeVisitor visitor);}
class Circle implements Shape { double radius; public double accept(ShapeVisitor v) { return v.visitCircle(this); }}
class AreaCalculator implements ShapeVisitor { public double visitCircle(Circle c) { return 3.14 * c.radius * c.radius; } public double visitRectangle(Rectangle r) { return 0; }}interface ShapeVisitor { visitCircle(circle: Circle): number; visitRectangle(rect: Rectangle): number;}
interface Shape { accept(visitor: ShapeVisitor): number;}
class Circle implements Shape { constructor(public radius: number) {} accept(visitor: ShapeVisitor) { return visitor.visitCircle(this); }}
class AreaCalculator implements ShapeVisitor { visitCircle(c: Circle) { return 3.14 * c.radius ** 2; } visitRectangle(r: Rectangle) { return 0; }}class Circle;class Rectangle;
class ShapeVisitor {public: virtual double visitCircle(Circle* c) = 0; virtual double visitRectangle(Rectangle* r) = 0;};
class Shape {public: virtual double accept(ShapeVisitor* v) = 0;};
class Circle : public Shape {public: double radius; double accept(ShapeVisitor* v) override { return v->visitCircle(this); }};
class AreaCalculator : public ShapeVisitor {public: double visitCircle(Circle* c) override { return 3.14 * c->radius * c->radius; } double visitRectangle(Rectangle* r) override { return 0; }};interface IShapeVisitor { double VisitCircle(Circle circle); double VisitRectangle(Rectangle rect);}
interface IShape { double Accept(IShapeVisitor visitor);}
class Circle : IShape { public double Radius; public double Accept(IShapeVisitor v) => v.VisitCircle(this);}
class AreaCalculator : IShapeVisitor { public double VisitCircle(Circle c) => 3.14 * c.Radius * c.Radius; public double VisitRectangle(Rectangle r) => 0;}type ShapeV interface { VisitCircle(*CircleV) VisitRectangle(*RectangleV)}
type ShapeEl interface { Accept(ShapeV) }
type CircleV struct{ Radius float64 }func (c *CircleV) Accept(v ShapeV) { v.VisitCircle(c) }
type RectangleV struct{ Width, Height float64 }func (r *RectangleV) Accept(v ShapeV) { v.VisitRectangle(r) }
type AreaCalc struct{ Total float64 }func (a *AreaCalc) VisitCircle(c *CircleV) { a.Total += math.Pi * c.Radius * c.Radius }func (a *AreaCalc) VisitRectangle(r *RectangleV) { a.Total += r.Width * r.Height }// Simple Exampletrait ShapeVisitor { fn visit_circle(&mut self, radius: f64); fn visit_rectangle(&mut self, width: f64, height: f64);}trait Shape { fn accept(&self, visitor: &mut dyn ShapeVisitor);}struct Circle { radius: f64,}impl Shape for Circle { fn accept(&self, visitor: &mut dyn ShapeVisitor) { visitor.visit_circle(self.radius); }}When to Use?
Section titled “When to Use?”✅ Object structure is stable
✅ Operations change often
✅ Need type-specific operations
✅ Need to accumulate state
When NOT to Use?
Section titled “When NOT to Use?”❌ Element types change often
❌ Few simple operations
❌ Don’t need type-specific handling
Key Takeaways
Section titled “Key Takeaways”- Visitor Pattern = Operations on object structures
- Double Dispatch = Choose by both types
- Visitor = One operation, many element types
- Element = Accepts visitors
- Trade-off = Easy to add operations, hard to add elements
Interview Focus: Visitor Pattern
Section titled “Interview Focus: Visitor Pattern”Key Points to Remember
Section titled “Key Points to Remember”1. Core Concept
Section titled “1. Core Concept”What to say:
“Visitor Pattern is a behavioral design pattern that separates algorithms from the objects they operate on. It lets you add new operations to existing class hierarchies without modifying them. The key mechanism is double dispatch - the operation executed depends on both the element type AND the visitor type.”
Why it matters:
- Shows you understand the fundamental purpose
- Demonstrates knowledge of double dispatch
- Indicates you understand the trade-offs
2. Double Dispatch
Section titled “2. Double Dispatch”Must explain:
- Single dispatch: Method called based on ONE type (receiver)
- Double dispatch: Method called based on TWO types
- How visitor achieves it: element.accept() → visitor.visit_X()
Example to give:
“Normal method calls use single dispatch - the method called depends on the object’s type. Visitor achieves double dispatch: first accept() is called on the element (first dispatch based on element type), then that calls visitor.visit_X(this) (second dispatch based on visitor type). This lets us choose behavior based on BOTH types.”
Must discuss:
- Visitor - Multiple operations, multiple element types
- Strategy - One operation, multiple algorithms
- Key difference - Visitor separates algorithm from objects; Strategy swaps algorithms
Example to give:
“Strategy Pattern is about swapping ONE algorithm - like choosing between QuickSort and MergeSort for sorting. Visitor Pattern is about performing MANY operations across MANY types - like exporting documents to HTML, Markdown, and PDF. Each visitor is an operation that handles all element types.”
4. Trade-offs
Section titled “4. Trade-offs”Benefits to mention:
- Easy to add operations - One new visitor class
- Single Responsibility - Each visitor = one operation
- Related code together - All HTML logic in one place
- Accumulate state - Count, aggregate as you visit
Trade-offs to acknowledge:
- Hard to add elements - Add visit method to ALL visitors
- Breaking encapsulation - Visitors need element internals
- Complexity - More classes, double dispatch
5. Common Interview Questions
Section titled “5. Common Interview Questions”Q: “When would you NOT use Visitor Pattern?”
A:
“I wouldn’t use Visitor when the element hierarchy changes frequently. Every new element type requires adding a visit method to EVERY visitor - that’s the opposite of Open/Closed Principle. If you’re adding new shapes regularly but operations are stable, use polymorphism instead (each shape implements its own operations).”
Q: “How does Visitor Pattern handle the expression problem?”
A:
“The expression problem is the difficulty of adding both new types AND new operations in a type-safe way. Visitor Pattern solves half of it - it makes adding new operations easy (new visitor class), but makes adding new types hard (modify all visitors). For systems where operations change more than types, Visitor is perfect.”
Q: “Where is Visitor Pattern commonly used?”
A:
“Visitor is common in compilers and interpreters - the AST structure is stable but you need many operations: type checking, code generation, optimization, pretty printing. Document processors use it too - document elements are stable but you need HTML export, PDF export, word count, spell check, etc.”
Interview Checklist
Section titled “Interview Checklist”Before your interview, make sure you can:
- Define Visitor Pattern clearly
- Explain double dispatch
- Compare Visitor vs Strategy
- Implement Visitor from scratch
- Explain the trade-off (operations vs types)
- List benefits and drawbacks
- Give real-world examples (compilers, document exporters)
- Discuss when NOT to use it
- Draw the class structure
Remember: Visitor Pattern is about adding new operations without modifying classes - separate algorithms from the objects they operate on! 🎯