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:
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”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"
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:
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”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
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”Mistake 2: Forgetting to Add Visit Methods for New Elements
Section titled “Mistake 2: Forgetting to Add Visit Methods for New Elements”Mistake 3: Using Visitor When Structure Changes Often
Section titled “Mistake 3: Using Visitor When Structure Changes Often”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”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 ** 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! 🎯