Bird
Raised Fist0
LLDsystem_design~7 mins

Visitor pattern in LLD - System Design Guide

Choose your learning style10 modes available

Start learning this pattern below

Jump into concepts and practice - no test required

or
Recommended
Test this pattern10 questions across easy, medium, and hard to know if this pattern is strong
Problem Statement
When you add new operations to a complex object structure, modifying each class to add these operations leads to code duplication and violates the open/closed principle. This makes the system hard to maintain and extend without risking bugs.
Solution
The Visitor pattern separates operations from the object structure by letting you define new operations in separate visitor classes. Each visitor implements the operation for each object type, and objects accept visitors to perform the operation without changing their own code.
Architecture
Object A
┌─────────┐
Visitor
┌───────────────┐ ┌─────────────┐

This diagram shows objects A and B each having an accept method that takes a visitor. The visitor has specific visit methods for each object type, separating operations from object structures.

Trade-offs
✓ Pros
Allows adding new operations without modifying existing object classes.
Keeps related operations together in visitor classes, improving code organization.
Supports complex object structures with multiple element types.
Promotes adherence to the open/closed principle.
✗ Cons
Adding new object types requires modifying all visitor classes, reducing flexibility.
Can increase complexity by introducing additional visitor classes and interfaces.
May lead to tight coupling between visitor and element classes.
Use when you have a stable set of object classes but need to add many new unrelated operations frequently, especially in complex object structures.
Avoid when the object structure changes often by adding new element types, as this requires updating all visitor implementations.
Real World Examples
Eclipse IDE
Uses Visitor pattern to separate operations like syntax checking, formatting, and refactoring on abstract syntax trees without modifying the tree node classes.
ANTLR (parser generator)
Applies Visitor pattern to traverse parse trees and perform different actions like interpretation or code generation without changing the tree node classes.
Code Example
Before applying Visitor, adding a new operation requires modifying each element class, violating open/closed principle. After applying Visitor, new operations are added as new visitor classes without changing element classes, improving extensibility and separation of concerns.
LLD
### Before (without Visitor pattern):

class ElementA:
    def operation1(self):
        print("ElementA operation1")

    def operation2(self):
        print("ElementA operation2")

class ElementB:
    def operation1(self):
        print("ElementB operation1")

    def operation2(self):
        print("ElementB operation2")

# Adding new operation requires modifying ElementA and ElementB

### After (with Visitor pattern):

class ElementA:
    def accept(self, visitor):
        visitor.visit_element_a(self)

class ElementB:
    def accept(self, visitor):
        visitor.visit_element_b(self)

class Visitor:
    def visit_element_a(self, element):
        pass

    def visit_element_b(self, element):
        pass

class Operation1Visitor(Visitor):
    def visit_element_a(self, element):
        print("ElementA operation1")

    def visit_element_b(self, element):
        print("ElementB operation1")

class Operation2Visitor(Visitor):
    def visit_element_a(self, element):
        print("ElementA operation2")

    def visit_element_b(self, element):
        print("ElementB operation2")

# Usage

elements = [ElementA(), ElementB()]

op1 = Operation1Visitor()
for e in elements:
    e.accept(op1)

op2 = Operation2Visitor()
for e in elements:
    e.accept(op2)
OutputSuccess
Alternatives
Strategy pattern
Encapsulates interchangeable algorithms inside classes and uses composition, focusing on varying behavior rather than operations on object structures.
Use when: Choose when you want to change the algorithm or behavior of a single object dynamically rather than adding operations across many object types.
Command pattern
Encapsulates a request as an object to parameterize clients with queues or logs, focusing on actions rather than operations on object structures.
Use when: Choose when you need to queue, log, or undo operations rather than separate operations from object structures.
Summary
Visitor pattern separates operations from object structures to add new behaviors without modifying existing classes.
It improves code organization and adheres to the open/closed principle by defining operations in visitor classes.
It is best used when object types are stable but operations change frequently.

Practice

(1/5)
1. What is the main purpose of the Visitor pattern in system design?
easy
A. To separate operations from the objects on which they operate
B. To create multiple instances of a class
C. To restrict access to certain parts of an object
D. To simplify database queries

Solution

  1. Step 1: Understand the Visitor pattern concept

    The Visitor pattern allows defining new operations on objects without changing their classes.
  2. Step 2: Identify the main goal

    Its main goal is to separate the operation logic from the object structure to keep code flexible.
  3. Final Answer:

    To separate operations from the objects on which they operate -> Option A
  4. Quick Check:

    Visitor pattern = separates operations [OK]
Hint: Visitor pattern separates operations from objects [OK]
Common Mistakes:
  • Confusing Visitor with Singleton pattern
  • Thinking it creates object instances
  • Assuming it controls access permissions
2. Which of the following is the correct method signature for a visitor interface method visiting an element called ElementA?
easy
A. void accept(ElementA element);
B. void acceptVisitor(ElementA visitor);
C. void visitElementA();
D. void visit(ElementA element);

Solution

  1. Step 1: Recall Visitor interface method naming

    The visitor interface defines methods named visit with the element type as parameter.
  2. Step 2: Match correct signature

    The correct signature is void visit(ElementA element); to visit ElementA.
  3. Final Answer:

    void visit(ElementA element); -> Option D
  4. Quick Check:

    Visitor method = visit(Element) [OK]
Hint: Visitor methods are named visit(Element) [OK]
Common Mistakes:
  • Confusing accept and visit method names
  • Using no parameters in visit method
  • Swapping visitor and element in parameters
3. Given the following code snippet, what will be the output?
class ElementA {
  accept(visitor) {
    visitor.visit(this);
  }
}

class PrintVisitor {
  visit(element) {
    console.log('Visited element');
  }
}

const element = new ElementA();
const visitor = new PrintVisitor();
element.accept(visitor);
medium
A. Visited ElementA
B. Error: visit is not a function
C. Visited element
D. No output

Solution

  1. Step 1: Trace accept method call

    The accept method calls visitor.visit(this), passing the element instance.
  2. Step 2: Check visit method behavior

    The visit method logs 'Visited element' to the console.
  3. Final Answer:

    Visited element -> Option C
  4. Quick Check:

    Visitor.visit logs message [OK]
Hint: Visitor.visit prints message when accept calls it [OK]
Common Mistakes:
  • Expecting element type name in output
  • Thinking visit method is missing
  • Assuming no output without explicit return
4. Identify the error in this Visitor pattern implementation:
class ElementB {
  accept(visitor) {
    visitor.accept(this);
  }
}

class ConcreteVisitor {
  visit(element) {
    console.log('Visiting element');
  }
}
medium
A. ElementB calls visitor.accept instead of visitor.visit
B. ConcreteVisitor should not have a visit method
C. accept method should return a value
D. ElementB should not have an accept method

Solution

  1. Step 1: Check accept method call

    The accept method calls visitor.accept(this), but visitor has no accept method.
  2. Step 2: Identify correct visitor method

    The visitor interface defines visit methods, so accept should call visitor.visit(this).
  3. Final Answer:

    ElementB calls visitor.accept instead of visitor.visit -> Option A
  4. Quick Check:

    accept calls visit, not accept [OK]
Hint: accept calls visitor.visit, not visitor.accept [OK]
Common Mistakes:
  • Confusing method names accept and visit
  • Expecting accept to return a value
  • Removing accept method from element
5. You have a system with multiple element types and want to add a new operation without modifying existing element classes. How does the Visitor pattern help in this scenario?
hard
A. By using inheritance to extend element classes with new operations
B. By creating a new visitor class implementing the operation for all element types
C. By adding new methods to each element class directly
D. By storing operations inside element objects as data

Solution

  1. Step 1: Understand the problem of adding new operations

    Modifying existing element classes is risky and breaks encapsulation.
  2. Step 2: Apply Visitor pattern solution

    Create a new visitor class that implements the new operation for all element types, keeping element classes unchanged.
  3. Final Answer:

    By creating a new visitor class implementing the operation for all element types -> Option B
  4. Quick Check:

    Visitor adds operations via new visitor classes [OK]
Hint: Add new visitor class for new operations [OK]
Common Mistakes:
  • Modifying element classes directly
  • Using inheritance to add operations
  • Embedding operations as data in elements