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:
sequenceDiagram
participant Client
participant Circle
participant AreaCalculator
Client->>Circle: accept(areaCalculator)
activate Circle
Note right of Circle: First dispatch:\nbased on Circle type
Circle->>AreaCalculator: visit_circle(this)
activate AreaCalculator
Note right of AreaCalculator: Second dispatch:\nbased on visitor type
AreaCalculator->>AreaCalculator: Calculate π × r²
AreaCalculator-->>Circle: return area
deactivate AreaCalculator
Circle-->>Client: return result
deactivate Circle
Note over Client,AreaCalculator: Double dispatch allows different<br/>behavior based on BOTH types!
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:
1# ❌ Without Visitor Pattern - Operations in every class!2
3import math4
5class Circle:6 def __init__(self, radius: float):7 self.radius = radius8
9 def area(self) -> float:10 return math.pi * self.radius ** 211
12 def perimeter(self) -> float:13 return 2 * math.pi * self.radius14
15 def draw(self) -> str:16 return f"Drawing circle with radius {self.radius}"17
18 # Adding new operation? Add method here!19 def export_svg(self) -> str:20 return f'<circle r="{self.radius}"/>'21
22class Rectangle:23 def __init__(self, width: float, height: float):24 self.width = width25 self.height = height26
27 def area(self) -> float:28 return self.width * self.height29
30 def perimeter(self) -> float:31 return 2 * (self.width + self.height)32
33 def draw(self) -> str:34 return f"Drawing rectangle {self.width}x{self.height}"35
36 # Adding new operation? Add method here too!37 def export_svg(self) -> str:38 return f'<rect width="{self.width}" height="{self.height}"/>'39
40class Triangle:41 def __init__(self, a: float, b: float, c: float):42 self.a, self.b, self.c = a, b, c43
44 def area(self) -> float:45 s = (self.a + self.b + self.c) / 246 return math.sqrt(s * (s-self.a) * (s-self.b) * (s-self.c))47
48 def perimeter(self) -> float:49 return self.a + self.b + self.c50
51 def draw(self) -> str:52 return f"Drawing triangle with sides {self.a}, {self.b}, {self.c}"53
54 # And here... for EVERY shape!55 def export_svg(self) -> str:56 return f'<polygon points="..."/>'57
58# Problems:59# - Every new operation = modify ALL shape classes60# - Related code (all area calcs) spread across files61# - Shapes know about SVG, drawing, etc. (too much responsibility)62# - Violates Open/Closed Principle1// ❌ Without Visitor Pattern - Operations in every class!2
3class Circle {4 private double radius;5
6 public Circle(double radius) {7 this.radius = radius;8 }9
10 public double area() {11 return Math.PI * radius * radius;12 }13
14 public double perimeter() {15 return 2 * Math.PI * radius;16 }17
18 public String draw() {19 return "Drawing circle with radius " + radius;20 }21
22 // Adding new operation? Add method here!23 public String exportSvg() {24 return "<circle r=\"" + radius + "\"/>";25 }26}27
28class Rectangle {29 private double width, height;30
31 public Rectangle(double width, double height) {32 this.width = width;33 this.height = height;34 }35
36 public double area() {37 return width * height;38 }39
40 public double perimeter() {41 return 2 * (width + height);42 }43
44 public String draw() {45 return "Drawing rectangle " + width + "x" + height;46 }47
48 // Adding new operation? Add method here too!49 public String exportSvg() {50 return "<rect width=\"" + width + "\" height=\"" + height + "\"/>";51 }52}53
54// Problems:55// - Every new operation = modify ALL shape classes56// - Related code (all area calcs) spread across files57// - Shapes know about SVG, drawing, etc. (too much responsibility)58// - Violates Open/Closed PrincipleProblems:
- 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”classDiagram
class Shape {
<<interface>>
+accept(visitor) void
}
class Circle {
-radius: float
+accept(visitor) void
+get_radius() float
}
class Rectangle {
-width: float
-height: float
+accept(visitor) void
+get_width() float
+get_height() float
}
class ShapeVisitor {
<<interface>>
+visit_circle(circle) void
+visit_rectangle(rect) void
}
class AreaCalculator {
-total_area: float
+visit_circle(circle) void
+visit_rectangle(rect) void
+get_total() float
}
class PerimeterCalculator {
-total_perimeter: float
+visit_circle(circle) void
+visit_rectangle(rect) void
+get_total() float
}
class SVGExporter {
-svg_elements: List
+visit_circle(circle) void
+visit_rectangle(rect) void
+get_svg() str
}
Shape <|.. Circle : implements
Shape <|.. Rectangle : implements
ShapeVisitor <|.. AreaCalculator : implements
ShapeVisitor <|.. PerimeterCalculator : implements
ShapeVisitor <|.. SVGExporter : implements
Shape --> ShapeVisitor : accepts
note for Shape "Elements accept visitors"
note for ShapeVisitor "Visitors define operations"
1from abc import ABC, abstractmethod2import math3from typing import List4
5# Step 1: Define the Visitor interface6class ShapeVisitor(ABC):7 """Visitor interface - defines visit methods for each shape type"""8
9 @abstractmethod10 def visit_circle(self, circle: 'Circle') -> None:11 pass12
13 @abstractmethod14 def visit_rectangle(self, rectangle: 'Rectangle') -> None:15 pass16
17 @abstractmethod18 def visit_triangle(self, triangle: 'Triangle') -> None:19 pass20
21# Step 2: Define the Element interface22class Shape(ABC):23 """Element interface - shapes accept visitors"""24
25 @abstractmethod26 def accept(self, visitor: ShapeVisitor) -> None:27 """Accept a visitor to perform an operation"""28 pass29
30# Step 3: Create Concrete Elements31class Circle(Shape):32 """Concrete element - circle shape"""33
34 def __init__(self, radius: float):35 self.radius = radius36
37 def accept(self, visitor: ShapeVisitor) -> None:38 visitor.visit_circle(self)39
40class Rectangle(Shape):41 """Concrete element - rectangle shape"""42
43 def __init__(self, width: float, height: float):44 self.width = width45 self.height = height46
47 def accept(self, visitor: ShapeVisitor) -> None:48 visitor.visit_rectangle(self)49
50class Triangle(Shape):51 """Concrete element - triangle shape"""52
53 def __init__(self, a: float, b: float, c: float):54 self.a = a55 self.b = b56 self.c = c57
58 def accept(self, visitor: ShapeVisitor) -> None:59 visitor.visit_triangle(self)60
61# Step 4: Create Concrete Visitors62class AreaCalculator(ShapeVisitor):63 """Visitor that calculates total area"""64
65 def __init__(self):66 self.total_area = 0.067
68 def visit_circle(self, circle: Circle) -> None:69 area = math.pi * circle.radius ** 270 self.total_area += area71 print(f" ⭕ Circle area: {area:.2f}")72
73 def visit_rectangle(self, rectangle: Rectangle) -> None:74 area = rectangle.width * rectangle.height75 self.total_area += area76 print(f" ⬜ Rectangle area: {area:.2f}")77
78 def visit_triangle(self, triangle: Triangle) -> None:79 s = (triangle.a + triangle.b + triangle.c) / 280 area = math.sqrt(s * (s-triangle.a) * (s-triangle.b) * (s-triangle.c))81 self.total_area += area82 print(f" 🔺 Triangle area: {area:.2f}")83
84 def get_total(self) -> float:85 return self.total_area86
87class PerimeterCalculator(ShapeVisitor):88 """Visitor that calculates total perimeter"""89
90 def __init__(self):91 self.total_perimeter = 0.092
93 def visit_circle(self, circle: Circle) -> None:94 perimeter = 2 * math.pi * circle.radius95 self.total_perimeter += perimeter96 print(f" ⭕ Circle perimeter: {perimeter:.2f}")97
98 def visit_rectangle(self, rectangle: Rectangle) -> None:99 perimeter = 2 * (rectangle.width + rectangle.height)100 self.total_perimeter += perimeter101 print(f" ⬜ Rectangle perimeter: {perimeter:.2f}")102
103 def visit_triangle(self, triangle: Triangle) -> None:104 perimeter = triangle.a + triangle.b + triangle.c105 self.total_perimeter += perimeter106 print(f" 🔺 Triangle perimeter: {perimeter:.2f}")107
108 def get_total(self) -> float:109 return self.total_perimeter110
111class SVGExporter(ShapeVisitor):112 """Visitor that exports shapes to SVG"""113
114 def __init__(self):115 self.elements: List[str] = []116
117 def visit_circle(self, circle: Circle) -> None:118 svg = f'<circle cx="50" cy="50" r="{circle.radius}" fill="blue"/>'119 self.elements.append(svg)120 print(f" ⭕ Exported circle to SVG")121
122 def visit_rectangle(self, rectangle: Rectangle) -> None:123 svg = f'<rect x="10" y="10" width="{rectangle.width}" height="{rectangle.height}" fill="green"/>'124 self.elements.append(svg)125 print(f" ⬜ Exported rectangle to SVG")126
127 def visit_triangle(self, triangle: Triangle) -> None:128 svg = f'<polygon points="50,10 10,90 90,90" fill="red"/>'129 self.elements.append(svg)130 print(f" 🔺 Exported triangle to SVG")131
132 def get_svg(self) -> str:133 elements = "\n ".join(self.elements)134 return f'<svg xmlns="http://www.w3.org/2000/svg">\n {elements}\n</svg>'135
136# Step 5: Use the pattern137def main():138 print("=" * 60)139 print("Shape Calculator (Visitor Pattern)")140 print("=" * 60)141
142 # Create shapes143 shapes: List[Shape] = [144 Circle(5),145 Rectangle(4, 6),146 Triangle(3, 4, 5),147 Circle(3),148 ]149
150 # Calculate areas151 print("\n📐 Calculating Areas:")152 print("-" * 40)153 area_calc = AreaCalculator()154 for shape in shapes:155 shape.accept(area_calc)156 print(f"\n 📊 Total Area: {area_calc.get_total():.2f}")157
158 # Calculate perimeters159 print("\n📏 Calculating Perimeters:")160 print("-" * 40)161 perimeter_calc = PerimeterCalculator()162 for shape in shapes:163 shape.accept(perimeter_calc)164 print(f"\n 📊 Total Perimeter: {perimeter_calc.get_total():.2f}")165
166 # Export to SVG167 print("\n🎨 Exporting to SVG:")168 print("-" * 40)169 svg_exporter = SVGExporter()170 for shape in shapes:171 shape.accept(svg_exporter)172 print(f"\n 📄 SVG Output:\n{svg_exporter.get_svg()}")173
174 # Easy to add new operation - just create new visitor!175 print("\n" + "=" * 60)176 print("✅ Visitor Pattern: New operations without modifying shapes!")177 print("✅ Adding new operation = Just one new visitor class!")178
179if __name__ == "__main__":180 main()1import java.util.*;2
3// Step 1: Define the Visitor interface4interface ShapeVisitor {5 /**6 * Visitor interface - defines visit methods for each shape type7 */8 void visitCircle(Circle circle);9 void visitRectangle(Rectangle rectangle);10 void visitTriangle(Triangle triangle);11}12
13// Step 2: Define the Element interface14interface Shape {15 /**16 * Element interface - shapes accept visitors17 */18 void accept(ShapeVisitor visitor);19}20
21// Step 3: Create Concrete Elements22class Circle implements Shape {23 /**24 * Concrete element - circle shape25 */26 private double radius;27
28 public Circle(double radius) {29 this.radius = radius;30 }31
32 public double getRadius() {33 return radius;34 }35
36 @Override37 public void accept(ShapeVisitor visitor) {38 visitor.visitCircle(this);39 }40}41
42class Rectangle implements Shape {43 /**44 * Concrete element - rectangle shape45 */46 private double width;47 private double height;48
49 public Rectangle(double width, double height) {50 this.width = width;51 this.height = height;52 }53
54 public double getWidth() { return width; }55 public double getHeight() { return height; }56
57 @Override58 public void accept(ShapeVisitor visitor) {59 visitor.visitRectangle(this);60 }61}62
63class Triangle implements Shape {64 /**65 * Concrete element - triangle shape66 */67 private double a, b, c;68
69 public Triangle(double a, double b, double c) {70 this.a = a;71 this.b = b;72 this.c = c;73 }74
75 public double getA() { return a; }76 public double getB() { return b; }77 public double getC() { return c; }78
79 @Override80 public void accept(ShapeVisitor visitor) {81 visitor.visitTriangle(this);82 }83}84
85// Step 4: Create Concrete Visitors86class AreaCalculator implements ShapeVisitor {87 /**88 * Visitor that calculates total area89 */90 private double totalArea = 0.0;91
92 @Override93 public void visitCircle(Circle circle) {94 double area = Math.PI * circle.getRadius() * circle.getRadius();95 totalArea += area;96 System.out.printf(" ⭕ Circle area: %.2f%n", area);97 }98
99 @Override100 public void visitRectangle(Rectangle rectangle) {101 double area = rectangle.getWidth() * rectangle.getHeight();102 totalArea += area;103 System.out.printf(" ⬜ Rectangle area: %.2f%n", area);104 }105
106 @Override107 public void visitTriangle(Triangle triangle) {108 double s = (triangle.getA() + triangle.getB() + triangle.getC()) / 2;109 double area = Math.sqrt(s * (s - triangle.getA()) *110 (s - triangle.getB()) *111 (s - triangle.getC()));112 totalArea += area;113 System.out.printf(" 🔺 Triangle area: %.2f%n", area);114 }115
116 public double getTotal() {117 return totalArea;118 }119}120
121class PerimeterCalculator implements ShapeVisitor {122 /**123 * Visitor that calculates total perimeter124 */125 private double totalPerimeter = 0.0;126
127 @Override128 public void visitCircle(Circle circle) {129 double perimeter = 2 * Math.PI * circle.getRadius();130 totalPerimeter += perimeter;131 System.out.printf(" ⭕ Circle perimeter: %.2f%n", perimeter);132 }133
134 @Override135 public void visitRectangle(Rectangle rectangle) {136 double perimeter = 2 * (rectangle.getWidth() + rectangle.getHeight());137 totalPerimeter += perimeter;138 System.out.printf(" ⬜ Rectangle perimeter: %.2f%n", perimeter);139 }140
141 @Override142 public void visitTriangle(Triangle triangle) {143 double perimeter = triangle.getA() + triangle.getB() + triangle.getC();144 totalPerimeter += perimeter;145 System.out.printf(" 🔺 Triangle perimeter: %.2f%n", perimeter);146 }147
148 public double getTotal() {149 return totalPerimeter;150 }151}152
153class SVGExporter implements ShapeVisitor {154 /**155 * Visitor that exports shapes to SVG156 */157 private List<String> elements = new ArrayList<>();158
159 @Override160 public void visitCircle(Circle circle) {161 String svg = String.format(162 "<circle cx=\"50\" cy=\"50\" r=\"%.0f\" fill=\"blue\"/>",163 circle.getRadius());164 elements.add(svg);165 System.out.println(" ⭕ Exported circle to SVG");166 }167
168 @Override169 public void visitRectangle(Rectangle rectangle) {170 String svg = String.format(171 "<rect x=\"10\" y=\"10\" width=\"%.0f\" height=\"%.0f\" fill=\"green\"/>",172 rectangle.getWidth(), rectangle.getHeight());173 elements.add(svg);174 System.out.println(" ⬜ Exported rectangle to SVG");175 }176
177 @Override178 public void visitTriangle(Triangle triangle) {179 String svg = "<polygon points=\"50,10 10,90 90,90\" fill=\"red\"/>";180 elements.add(svg);181 System.out.println(" 🔺 Exported triangle to SVG");182 }183
184 public String getSvg() {185 String content = String.join("\n ", elements);186 return "<svg xmlns=\"http://www.w3.org/2000/svg\">\n " + content + "\n</svg>";187 }188}189
190// Step 5: Use the pattern191public class Main {192 public static void main(String[] args) {193 System.out.println("=".repeat(60));194 System.out.println("Shape Calculator (Visitor Pattern)");195 System.out.println("=".repeat(60));196
197 // Create shapes198 List<Shape> shapes = Arrays.asList(199 new Circle(5),200 new Rectangle(4, 6),201 new Triangle(3, 4, 5),202 new Circle(3)203 );204
205 // Calculate areas206 System.out.println("\n📐 Calculating Areas:");207 System.out.println("-".repeat(40));208 AreaCalculator areaCalc = new AreaCalculator();209 for (Shape shape : shapes) {210 shape.accept(areaCalc);211 }212 System.out.printf("%n 📊 Total Area: %.2f%n", areaCalc.getTotal());213
214 // Calculate perimeters215 System.out.println("\n📏 Calculating Perimeters:");216 System.out.println("-".repeat(40));217 PerimeterCalculator perimeterCalc = new PerimeterCalculator();218 for (Shape shape : shapes) {219 shape.accept(perimeterCalc);220 }221 System.out.printf("%n 📊 Total Perimeter: %.2f%n", perimeterCalc.getTotal());222
223 // Export to SVG224 System.out.println("\n🎨 Exporting to SVG:");225 System.out.println("-".repeat(40));226 SVGExporter svgExporter = new SVGExporter();227 for (Shape shape : shapes) {228 shape.accept(svgExporter);229 }230 System.out.println("\n 📄 SVG Output:\n" + svgExporter.getSvg());231
232 System.out.println("\n" + "=".repeat(60));233 System.out.println("✅ Visitor Pattern: New operations without modifying shapes!");234 System.out.println("✅ Adding new operation = Just one new visitor class!");235 }236}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:
1# ❌ Without Visitor Pattern - Export methods in every element!2
3class Heading:4 def __init__(self, text: str, level: int):5 self.text = text6 self.level = level7
8 def to_html(self) -> str:9 return f"<h{self.level}>{self.text}</h{self.level}>"10
11 def to_markdown(self) -> str:12 return f"{'#' * self.level} {self.text}"13
14 def to_plain_text(self) -> str:15 return self.text.upper()16
17 # New format? Add method here AND in every other class!18
19class Paragraph:20 def __init__(self, text: str):21 self.text = text22
23 def to_html(self) -> str:24 return f"<p>{self.text}</p>"25
26 def to_markdown(self) -> str:27 return self.text28
29 def to_plain_text(self) -> str:30 return self.text31
32 # New format? Add method here too!33
34class CodeBlock:35 def __init__(self, code: str, language: str):36 self.code = code37 self.language = language38
39 def to_html(self) -> str:40 return f"<pre><code class='{self.language}'>{self.code}</code></pre>"41
42 def to_markdown(self) -> str:43 return f"```{self.language}\n{self.code}\n```"44
45 def to_plain_text(self) -> str:46 return self.code47
48 # And here... for EVERY element type!49
50# Problems:51# - 4 export formats × 5 element types = 20 methods!52# - Adding PDF export = add method to ALL classes53# - Related export code (all HTML) spread everywhere1// ❌ Without Visitor Pattern - Export methods in every element!2
3class Heading {4 private String text;5 private int level;6
7 public String toHtml() {8 return "<h" + level + ">" + text + "</h" + level + ">";9 }10
11 public String toMarkdown() {12 return "#".repeat(level) + " " + text;13 }14
15 public String toPlainText() {16 return text.toUpperCase();17 }18
19 // New format? Add method here AND in every other class!20}21
22class Paragraph {23 private String text;24
25 public String toHtml() {26 return "<p>" + text + "</p>";27 }28
29 public String toMarkdown() {30 return text;31 }32
33 public String toPlainText() {34 return text;35 }36
37 // New format? Add method here too!38}39
40// Problems:41// - 4 export formats × 5 element types = 20 methods!42// - Adding PDF export = add method to ALL classes43// - Related export code (all HTML) spread everywhereProblems:
- 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”classDiagram
class DocumentElement {
<<interface>>
+accept(visitor) void
}
class Heading {
-text: str
-level: int
+accept(visitor) void
}
class Paragraph {
-text: str
+accept(visitor) void
}
class CodeBlock {
-code: str
-language: str
+accept(visitor) void
}
class DocumentVisitor {
<<interface>>
+visit_heading(heading) void
+visit_paragraph(para) void
+visit_code_block(code) void
}
class HTMLExporter {
+visit_heading(heading) void
+visit_paragraph(para) void
+visit_code_block(code) void
+get_output() str
}
class MarkdownExporter {
+visit_heading(heading) void
+visit_paragraph(para) void
+visit_code_block(code) void
+get_output() str
}
DocumentElement <|.. Heading : implements
DocumentElement <|.. Paragraph : implements
DocumentElement <|.. CodeBlock : implements
DocumentVisitor <|.. HTMLExporter : implements
DocumentVisitor <|.. MarkdownExporter : implements
DocumentElement --> DocumentVisitor : accepts
1from abc import ABC, abstractmethod2from typing import List3
4# Step 1: Define the Visitor interface5class DocumentVisitor(ABC):6 """Visitor interface for document operations"""7
8 @abstractmethod9 def visit_heading(self, heading: 'Heading') -> None:10 pass11
12 @abstractmethod13 def visit_paragraph(self, paragraph: 'Paragraph') -> None:14 pass15
16 @abstractmethod17 def visit_code_block(self, code_block: 'CodeBlock') -> None:18 pass19
20 @abstractmethod21 def visit_image(self, image: 'Image') -> None:22 pass23
24# Step 2: Define the Element interface25class DocumentElement(ABC):26 """Element interface - document elements accept visitors"""27
28 @abstractmethod29 def accept(self, visitor: DocumentVisitor) -> None:30 pass31
32# Step 3: Create Concrete Elements33class Heading(DocumentElement):34 """Heading element"""35
36 def __init__(self, text: str, level: int = 1):37 self.text = text38 self.level = min(max(level, 1), 6) # H1-H639
40 def accept(self, visitor: DocumentVisitor) -> None:41 visitor.visit_heading(self)42
43class Paragraph(DocumentElement):44 """Paragraph element"""45
46 def __init__(self, text: str):47 self.text = text48
49 def accept(self, visitor: DocumentVisitor) -> None:50 visitor.visit_paragraph(self)51
52class CodeBlock(DocumentElement):53 """Code block element"""54
55 def __init__(self, code: str, language: str = ""):56 self.code = code57 self.language = language58
59 def accept(self, visitor: DocumentVisitor) -> None:60 visitor.visit_code_block(self)61
62class Image(DocumentElement):63 """Image element"""64
65 def __init__(self, src: str, alt: str = ""):66 self.src = src67 self.alt = alt68
69 def accept(self, visitor: DocumentVisitor) -> None:70 visitor.visit_image(self)71
72# Step 4: Create Concrete Visitors73class HTMLExporter(DocumentVisitor):74 """Exports document to HTML"""75
76 def __init__(self):77 self.output: List[str] = []78
79 def visit_heading(self, heading: Heading) -> None:80 self.output.append(81 f"<h{heading.level}>{heading.text}</h{heading.level}>"82 )83
84 def visit_paragraph(self, paragraph: Paragraph) -> None:85 self.output.append(f"<p>{paragraph.text}</p>")86
87 def visit_code_block(self, code_block: CodeBlock) -> None:88 lang = f' class="language-{code_block.language}"' if code_block.language else ''89 self.output.append(90 f"<pre><code{lang}>{code_block.code}</code></pre>"91 )92
93 def visit_image(self, image: Image) -> None:94 self.output.append(95 f'<img src="{image.src}" alt="{image.alt}"/>'96 )97
98 def get_output(self) -> str:99 return "\n".join(self.output)100
101class MarkdownExporter(DocumentVisitor):102 """Exports document to Markdown"""103
104 def __init__(self):105 self.output: List[str] = []106
107 def visit_heading(self, heading: Heading) -> None:108 self.output.append(f"{'#' * heading.level} {heading.text}")109
110 def visit_paragraph(self, paragraph: Paragraph) -> None:111 self.output.append(paragraph.text)112
113 def visit_code_block(self, code_block: CodeBlock) -> None:114 self.output.append(f"```{code_block.language}")115 self.output.append(code_block.code)116 self.output.append("```")117
118 def visit_image(self, image: Image) -> None:119 self.output.append(f"")120
121 def get_output(self) -> str:122 return "\n\n".join(self.output)123
124class PlainTextExporter(DocumentVisitor):125 """Exports document to plain text"""126
127 def __init__(self):128 self.output: List[str] = []129
130 def visit_heading(self, heading: Heading) -> None:131 self.output.append(heading.text.upper())132 self.output.append("=" * len(heading.text))133
134 def visit_paragraph(self, paragraph: Paragraph) -> None:135 self.output.append(paragraph.text)136
137 def visit_code_block(self, code_block: CodeBlock) -> None:138 self.output.append("--- CODE ---")139 self.output.append(code_block.code)140 self.output.append("--- END ---")141
142 def visit_image(self, image: Image) -> None:143 self.output.append(f"[Image: {image.alt}]")144
145 def get_output(self) -> str:146 return "\n\n".join(self.output)147
148class WordCounter(DocumentVisitor):149 """Counts words in the document"""150
151 def __init__(self):152 self.word_count = 0153 self.code_lines = 0154 self.image_count = 0155
156 def visit_heading(self, heading: Heading) -> None:157 self.word_count += len(heading.text.split())158
159 def visit_paragraph(self, paragraph: Paragraph) -> None:160 self.word_count += len(paragraph.text.split())161
162 def visit_code_block(self, code_block: CodeBlock) -> None:163 self.code_lines += len(code_block.code.split('\n'))164
165 def visit_image(self, image: Image) -> None:166 self.image_count += 1167
168 def get_stats(self) -> dict:169 return {170 "words": self.word_count,171 "code_lines": self.code_lines,172 "images": self.image_count173 }174
175# Step 5: Create a Document class to hold elements176class Document:177 """Document containing multiple elements"""178
179 def __init__(self, title: str):180 self.title = title181 self.elements: List[DocumentElement] = []182
183 def add(self, element: DocumentElement) -> 'Document':184 self.elements.append(element)185 return self186
187 def accept(self, visitor: DocumentVisitor) -> None:188 """Let visitor visit all elements"""189 for element in self.elements:190 element.accept(visitor)191
192# Step 6: Use the pattern193def main():194 print("=" * 60)195 print("Document Exporter (Visitor Pattern)")196 print("=" * 60)197
198 # Create a document199 doc = Document("My Article")200 doc.add(Heading("Introduction to Design Patterns", 1))201 doc.add(Paragraph("Design patterns are reusable solutions to common problems in software design."))202 doc.add(Heading("Example Code", 2))203 doc.add(CodeBlock("def hello():\n print('Hello, World!')", "python"))204 doc.add(Paragraph("The code above demonstrates a simple function."))205 doc.add(Image("diagram.png", "Class Diagram"))206
207 # Export to HTML208 print("\n📄 HTML Export:")209 print("-" * 40)210 html_exporter = HTMLExporter()211 doc.accept(html_exporter)212 print(html_exporter.get_output())213
214 # Export to Markdown215 print("\n📝 Markdown Export:")216 print("-" * 40)217 md_exporter = MarkdownExporter()218 doc.accept(md_exporter)219 print(md_exporter.get_output())220
221 # Export to Plain Text222 print("\n📃 Plain Text Export:")223 print("-" * 40)224 text_exporter = PlainTextExporter()225 doc.accept(text_exporter)226 print(text_exporter.get_output())227
228 # Get document statistics229 print("\n📊 Document Statistics:")230 print("-" * 40)231 counter = WordCounter()232 doc.accept(counter)233 stats = counter.get_stats()234 print(f" Words: {stats['words']}")235 print(f" Code Lines: {stats['code_lines']}")236 print(f" Images: {stats['images']}")237
238 print("\n" + "=" * 60)239 print("✅ Visitor Pattern: New export format = Just one new visitor!")240 print("✅ All HTML logic in HTMLExporter - easy to maintain!")241
242if __name__ == "__main__":243 main()1import java.util.*;2
3// Step 1: Define the Visitor interface4interface DocumentVisitor {5 /**6 * Visitor interface for document operations7 */8 void visitHeading(Heading heading);9 void visitParagraph(Paragraph paragraph);10 void visitCodeBlock(CodeBlock codeBlock);11 void visitImage(Image image);12}13
14// Step 2: Define the Element interface15interface DocumentElement {16 /**17 * Element interface - document elements accept visitors18 */19 void accept(DocumentVisitor visitor);20}21
22// Step 3: Create Concrete Elements23class Heading implements DocumentElement {24 private String text;25 private int level;26
27 public Heading(String text, int level) {28 this.text = text;29 this.level = Math.min(Math.max(level, 1), 6);30 }31
32 public String getText() { return text; }33 public int getLevel() { return level; }34
35 @Override36 public void accept(DocumentVisitor visitor) {37 visitor.visitHeading(this);38 }39}40
41class Paragraph implements DocumentElement {42 private String text;43
44 public Paragraph(String text) {45 this.text = text;46 }47
48 public String getText() { return text; }49
50 @Override51 public void accept(DocumentVisitor visitor) {52 visitor.visitParagraph(this);53 }54}55
56class CodeBlock implements DocumentElement {57 private String code;58 private String language;59
60 public CodeBlock(String code, String language) {61 this.code = code;62 this.language = language;63 }64
65 public String getCode() { return code; }66 public String getLanguage() { return language; }67
68 @Override69 public void accept(DocumentVisitor visitor) {70 visitor.visitCodeBlock(this);71 }72}73
74class Image implements DocumentElement {75 private String src;76 private String alt;77
78 public Image(String src, String alt) {79 this.src = src;80 this.alt = alt;81 }82
83 public String getSrc() { return src; }84 public String getAlt() { return alt; }85
86 @Override87 public void accept(DocumentVisitor visitor) {88 visitor.visitImage(this);89 }90}91
92// Step 4: Create Concrete Visitors93class HTMLExporter implements DocumentVisitor {94 private List<String> output = new ArrayList<>();95
96 @Override97 public void visitHeading(Heading heading) {98 output.add(String.format("<h%d>%s</h%d>",99 heading.getLevel(), heading.getText(), heading.getLevel()));100 }101
102 @Override103 public void visitParagraph(Paragraph paragraph) {104 output.add(String.format("<p>%s</p>", paragraph.getText()));105 }106
107 @Override108 public void visitCodeBlock(CodeBlock codeBlock) {109 String lang = codeBlock.getLanguage().isEmpty() ? "" :110 " class=\"language-" + codeBlock.getLanguage() + "\"";111 output.add(String.format("<pre><code%s>%s</code></pre>",112 lang, codeBlock.getCode()));113 }114
115 @Override116 public void visitImage(Image image) {117 output.add(String.format("<img src=\"%s\" alt=\"%s\"/>",118 image.getSrc(), image.getAlt()));119 }120
121 public String getOutput() {122 return String.join("\n", output);123 }124}125
126class MarkdownExporter implements DocumentVisitor {127 private List<String> output = new ArrayList<>();128
129 @Override130 public void visitHeading(Heading heading) {131 output.add("#".repeat(heading.getLevel()) + " " + heading.getText());132 }133
134 @Override135 public void visitParagraph(Paragraph paragraph) {136 output.add(paragraph.getText());137 }138
139 @Override140 public void visitCodeBlock(CodeBlock codeBlock) {141 output.add("```" + codeBlock.getLanguage());142 output.add(codeBlock.getCode());143 output.add("```");144 }145
146 @Override147 public void visitImage(Image image) {148 output.add(String.format("", image.getAlt(), image.getSrc()));149 }150
151 public String getOutput() {152 return String.join("\n\n", output);153 }154}155
156class WordCounter implements DocumentVisitor {157 private int wordCount = 0;158 private int codeLines = 0;159 private int imageCount = 0;160
161 @Override162 public void visitHeading(Heading heading) {163 wordCount += heading.getText().split("\\s+").length;164 }165
166 @Override167 public void visitParagraph(Paragraph paragraph) {168 wordCount += paragraph.getText().split("\\s+").length;169 }170
171 @Override172 public void visitCodeBlock(CodeBlock codeBlock) {173 codeLines += codeBlock.getCode().split("\n").length;174 }175
176 @Override177 public void visitImage(Image image) {178 imageCount++;179 }180
181 public Map<String, Integer> getStats() {182 Map<String, Integer> stats = new HashMap<>();183 stats.put("words", wordCount);184 stats.put("code_lines", codeLines);185 stats.put("images", imageCount);186 return stats;187 }188}189
190// Step 5: Document class191class Document {192 private String title;193 private List<DocumentElement> elements = new ArrayList<>();194
195 public Document(String title) {196 this.title = title;197 }198
199 public Document add(DocumentElement element) {200 elements.add(element);201 return this;202 }203
204 public void accept(DocumentVisitor visitor) {205 for (DocumentElement element : elements) {206 element.accept(visitor);207 }208 }209}210
211// Step 6: Use the pattern212public class Main {213 public static void main(String[] args) {214 System.out.println("=".repeat(60));215 System.out.println("Document Exporter (Visitor Pattern)");216 System.out.println("=".repeat(60));217
218 // Create a document219 Document doc = new Document("My Article");220 doc.add(new Heading("Introduction to Design Patterns", 1))221 .add(new Paragraph("Design patterns are reusable solutions."))222 .add(new Heading("Example Code", 2))223 .add(new CodeBlock("def hello():\n print('Hello!')", "python"))224 .add(new Image("diagram.png", "Class Diagram"));225
226 // Export to HTML227 System.out.println("\n📄 HTML Export:");228 System.out.println("-".repeat(40));229 HTMLExporter htmlExporter = new HTMLExporter();230 doc.accept(htmlExporter);231 System.out.println(htmlExporter.getOutput());232
233 // Export to Markdown234 System.out.println("\n📝 Markdown Export:");235 System.out.println("-".repeat(40));236 MarkdownExporter mdExporter = new MarkdownExporter();237 doc.accept(mdExporter);238 System.out.println(mdExporter.getOutput());239
240 // Get statistics241 System.out.println("\n📊 Document Statistics:");242 System.out.println("-".repeat(40));243 WordCounter counter = new WordCounter();244 doc.accept(counter);245 Map<String, Integer> stats = counter.getStats();246 System.out.println(" Words: " + stats.get("words"));247 System.out.println(" Code Lines: " + stats.get("code_lines"));248 System.out.println(" Images: " + stats.get("images"));249
250 System.out.println("\n✅ Visitor Pattern: New format = One new visitor!");251 }252}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:
graph LR
A[element.accept visitor] -->|1st dispatch| B[concrete element type]
B -->|2nd dispatch| C[visitor.visit_X element]
C --> D[specific operation]
style A fill:#f9f,stroke:#333
style D fill:#9f9,stroke:#333
- 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”1# ❌ Bad: Visitor accesses private internals2class BadVisitor:3 def visit_circle(self, circle):4 # Bad: Accessing private attributes!5 area = 3.14 * circle._internal_radius ** 26
7# ✅ Good: Use public getters8class GoodVisitor:9 def visit_circle(self, circle):10 # Good: Uses public interface11 area = 3.14 * circle.get_radius() ** 21// ❌ Bad: Visitor accesses private fields2class BadVisitor implements ShapeVisitor {3 @Override4 public void visitCircle(Circle circle) {5 // Bad: Would need reflection or package access6 double area = Math.PI * circle.radius * circle.radius;7 }8}9
10// ✅ Good: Use public getters11class GoodVisitor implements ShapeVisitor {12 @Override13 public void visitCircle(Circle circle) {14 // Good: Uses public interface15 double area = Math.PI * circle.getRadius() * circle.getRadius();16 }17}Mistake 2: Forgetting to Add Visit Methods for New Elements
Section titled “Mistake 2: Forgetting to Add Visit Methods for New Elements”1# ❌ Bad: Adding new element without updating visitors2class Pentagon(Shape):3 def accept(self, visitor):4 visitor.visit_pentagon(self) # But visitors don't have this!5
6# ✅ Good: Use default implementation or abstract base7class ShapeVisitor(ABC):8 @abstractmethod9 def visit_circle(self, circle): pass10
11 @abstractmethod12 def visit_rectangle(self, rect): pass13
14 @abstractmethod15 def visit_pentagon(self, pentagon): pass # Add when adding Pentagon!1// ❌ Bad: Adding new element without updating interface2class Pentagon implements Shape {3 @Override4 public void accept(ShapeVisitor visitor) {5 visitor.visitPentagon(this); // Compile error!6 }7}8
9// ✅ Good: Update visitor interface when adding elements10interface ShapeVisitor {11 void visitCircle(Circle circle);12 void visitRectangle(Rectangle rectangle);13 void visitPentagon(Pentagon pentagon); // Add when adding Pentagon!14}Mistake 3: Using Visitor When Structure Changes Often
Section titled “Mistake 3: Using Visitor When Structure Changes Often”1# ❌ Bad: Using visitor when elements change frequently2# If you add Triangle, Square, Pentagon, Hexagon...3# Every visitor must be updated!4
5# ✅ Better: Use polymorphism when structure changes often6class Shape(ABC):7 @abstractmethod8 def area(self) -> float: pass9
10 @abstractmethod11 def perimeter(self) -> float: pass12
13# Each shape implements its own operations14# Adding new shape = just one new class1// ❌ Bad: Using visitor when elements change frequently2// If you add Triangle, Square, Pentagon, Hexagon...3// Every visitor must be updated!4
5// ✅ Better: Use polymorphism when structure changes often6interface Shape {7 double area();8 double perimeter();9}10
11// Each shape implements its own operations12// Adding new shape = just one new classBenefits 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”1Element.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”1class ShapeVisitor(ABC):2 @abstractmethod3 def visit_circle(self, circle): pass4 @abstractmethod5 def visit_rectangle(self, rect): pass6
7class Shape(ABC):8 @abstractmethod9 def accept(self, visitor): pass10
11class Circle(Shape):12 def accept(self, visitor):13 visitor.visit_circle(self) # Double dispatch!14
15class AreaCalculator(ShapeVisitor):16 def visit_circle(self, circle):17 return 3.14 * circle.radius ** 2When 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! 🎯