AlgoMaster Logo

Visitor Design Pattern

Ashish

Ashish Pratap Singh

4 min read

The Visitor Design Pattern is a behavioral pattern that lets you add new operations to existing object structures without modifying their classes.

It achieves this by allowing you to separate the algorithm from the objects it operates on.

It’s particularly useful in situations where:

  • You have a complex object structure (like ASTs, documents, or UI elements) that you want to perform multiple unrelated operations on.
  • You want to add new behaviors to classes without changing their source code.
  • You need to perform different actions depending on an object’s concrete type, without resorting to a long chain of if-else or instanceof checks.

The Visitor Pattern lets you externalize operations into separate visitor classes. Each visitor implements behavior for every element type, while the elements simply accept the visitor. This keeps your data structures clean and your logic modular and extensible.

Let’s walk through a real-world example to see how we can apply the Visitor Pattern to cleanly separate behavior from structure and make our system easier to extend without touching existing classes.

1. The Problem: Adding Operations to a Shape Hierarchy

Imagine you’re building a vector graphics editor that supports multiple shape types:

  • Circle
  • Rectangle
  • Triangle

Each shape is part of a common hierarchy and must support a variety of operations, such as:

  • Rendering on screen
  • Calculating area
  • Exporting to SVG
  • Serializing to JSON

The simplest approach is to add all of these methods to each shape class:

Why This Breaks Down

This solution seems fine for a couple of operations, but quickly becomes problematic as new operations or shape types are added.

1. Violates the Single Responsibility Principle

Each shape class now contains multiple unrelated responsibilities:

  • Geometry calculations
  • Drawing
  • Serialization
  • Exporting formats
  • Possibly printing, styling, etc.

This bloats the class and makes it harder to maintain.

2. Hard to Extend

If you need to add a new operation (e.g., generatePdf()), you must:

  • Modify every class in the hierarchy
  • Recompile everything
  • Possibly break existing logic

This violates the Open/Closed Principle — the classes should be open for extension but closed for modification.

3. You Don’t Always Control the Classes

What if the shape classes are part of a third-party library or generated code? You can't easily add new behavior directly.

What We Really Need

We need a solution that lets us:

  • Separate operations from the shape classes
  • Add new behaviors without modifying existing classes
  • Avoid duplicating instanceof checks or using type switches to handle different shapes

This is exactly what the Visitor Design Pattern is designed to solve.

2. What is the Visitor Pattern

The Visitor Design Pattern lets you separate algorithms from the objects on which they operate. It enables you to add new operations to a class hierarchy without modifying the classes themselves.

This is especially helpful when you have:

  • stable set of element classes (e.g., shapes)
  • growing set of operations that need to work across those classes (e.g., rendering, exporting, calculating)

Class Diagram

1. Element Interface (e.g., Shape)

  • Represents the objects in your object structure (such as graphical shapes, document nodes, AST elements).
  • Declares a single method: void accept(Visitor visitor);
  • Every class that wants to be visited must implement this interface.
  • This allows the visitor to be “accepted” into the object so it can perform operations on it.

2. Concrete Elements (e.g., CircleRectangle)

  • Implements the Element interface.
  • Inside the accept() method, they call back the visitor’s corresponding method using visitor.visitX(this).

3. Visitor Interface

  • Declares a set of visit() methods — one for each concrete element type.
  • Each method is tailored to handle a specific type of element.
  • This interface allows you to define external operations that apply to various elements in your model.

4. Concrete Visitors (e.g., AreaCalculatorVisitor)

  • Implements the Visitor interface.
  • Each visitor represents a specific operation that needs to be performed across elements.
  • Implement specific behaviors applied to elements (e.g., export, validate, transform)

3. Implementing Visitor Pattern

Lets refactor our graphics system with multiple shapes (CircleRectangle) using Visitor Pattern to perform two operations:

  • Calculate the area of each shape
  • Export the shape to SVG format

1. Define the Shape Interface (Element)

All shapes must accept a visitor.

2. Create Concrete Shape Classes (Elements)

Each shape class implements accept() and delegates to the visitor.

🔵 Circle

🟥 Rectangle

3. Define the Visitor Interface

Each method corresponds to a shape type.

4. Implement Concrete Visitors

📏 Area Calculator Visitor

🖼️ SVG Exporter Visitor

5. Client Code

Now you can operate on the shape structure using any visitor.

Output

What We Achieved

  • Decoupled logic: Shape classes are clean; logic lives in visitors
  • Open/Closed Principle: Easily add new visitors (e.g., JsonExporterVisitor) without touching shapes
  • Double dispatch: Eliminated need for instanceof or type-checking
  • Reusability & maintainability: Each visitor focuses on one operation and is testable in isolation