Visitor Design Pattern: An Easy Path

Visitor Design Pattern: An Easy Path

14 Sep 2024
Intermediate
10.6K Views
27 min read
Learn via Video Course & by Doing Hands-on Labs

⭐ .NET Design Patterns Course: Design Patterns in C# Online Training

Visitor Design Pattern

The Visitor Design Pattern is part of the behavioral design pattern in a gang of four design patterns. When we need to execute an operation on a collection of similar-in-kind objects, we utilize the visitor design pattern. By using the visitor design pattern, we may transfer the operational functionality from the objects to a different class.

In the Design Pattern tutorial, we will learn about what is the visitor design pattern?, why is visitor design pattern important?, components of visitor design pattern, implementation of visitor design pattern, real-world example of visitor design pattern, applications of visitor design pattern, advantages and disadvantages, and many more.

What is the Visitor Design Pattern?

Visitor Design Pattern is a behavioral design pattern that allows you to add new operations to objects without modifying their classes. It is particularly useful when you need to perform operations on a set of objects with different types, but you want to avoid cluttering the object classes with these operations.

  • Adds new operations to objects without modifying their classes.
  • Includes Visitor, ConcreteVisitor, Element, ConcreteElement, and Object Structure.
  • Uses double dispatch to call methods based on the element’s type.
  • Allows easy addition of new operations through new visitors without altering element classes.
  • Separates operations from the object structure, promoting cleaner design.
  • Ideal for scenarios where operations are stable but elements may change.
  • Introduces complexity and requires managing interactions between elements and visitors.
Read More:
Types of Design Patterns
IoC Container and DI Container: An Easy Guide
Different Types of Software Design Principles

UML Diagram of Visitor Design Pattern

UML Diagram of Visitor Design Pattern

Components of Visitor Design Pattern

There are five major components in visitor Design Patterns that are:

Components of Visitor Design Pattern

  1. Client
  2. Visitor
  3. Concrete Visitor
  4. Visitable
  5. ConcreteVisitable

1. Client

  • Sets Up Object Structure: Creates and manages the collection of elements that will be visited.
  • Creates and Passes Visitors: Instantiates visitor objects and passes them to the object structure.
  • Triggers Operations: Calls the accept method on the object structure to apply visitor operations to each element.

2. Visitor

  • Defines Operations: The Visitor interface or abstract class declares visit methods for each type of element it can operate on.
  • Concrete Implementation: Concrete visitors implement these methods, providing specific behaviors for each element type.
  • Performs Actions: Visitors perform operations on elements without modifying their classes, utilizing double dispatch to call the correct method based on the element’s type.

3. Concrete Visitor

  • Implements Visitor Interface: Provides specific implementations of the visit methods defined in the Visitor interface.
  • Defines Operations: Contains the logic for operations that should be performed on different types of elements.
  • Interacts with Elements: Executes actions on elements by invoking their accept method, applying the visitor's behavior to each element.

4. Visitable

  • Defines Accept Method: The Visitable interface or abstract class declares an accept method that takes a Visitor as a parameter.
  • Interacts with Visitor: Implements the accept method to call the appropriate visit method on the Visitor.
  • Enables Double Dispatch: Facilitates the visitor pattern’s double dispatch mechanism by allowing visitors to operate on specific types of elements.

5. ConcreteVisitable

  • Implements Visitable Interface: Provides a concrete implementation of the Visitable interface or abstract class.
  • Defines Accept Method: Implements the accept method to pass itself to the visitor, allowing the visitor to perform operations on it.
  • Specific Behavior: Represents a specific type of element with its own data and behavior, which can be operated on by different visitors.

Structure of Visitor Design Pattern

Structure of Visitor Design Pattern

1. The Visitor interface declares a collection of visiting methods that can accept tangible components of an object structure as inputs. If the program is written in a language that allows overloading, both methods may have the same names, but their argument types must differ.

2. Different versions of the same behaviors, customized for various concrete element classes, are implemented by each Concrete Visitor.

3. Visitors can be accepted via a method declared in the Element interface. The type of the visitor interface should be defined as the only parameter for this method.

4. Every Concrete Element needs to put the acceptance technique into practice. This method's goal is to route the call to the appropriate visitor's function that is associated with the element class that is currently in use. Note that all subclasses must override this method in their own classes and call the proper method on the visitor object, even if a base element class implements it.

5. Typically, the client represents a collection or another complicated object (such as a Composite tree). Since clients often interact with items from that collection through an abstract interface, they are typically unaware of all the concrete element classes.

Implementation of Visitor Design Pattern

The Visitor Design Pattern allows operations to be added to a class hierarchy without changing the classes themselves.

  • Visitor Interface: Defines methods for each type of element the visitor can operate on. This allows different operations to be performed on the elements.
  • Concrete Visitor: Implements the visitor interface by defining specific operations for each element type. For example, it may calculate prices or apply discounts.
  • Visitable (Element) Interface: Declares an accept method that takes a visitor as a parameter. This allows the element to accept a visitor and trigger the appropriate operation.
  • Concrete Visitable (Concrete Element): Implements the Visitable interface, representing specific types of elements (e.g., Book, Magazine). It defines how to handle the visitor and provides the data the visitor will operate on.
  • Client: The client creates and manages the objects (concrete visitable elements) and visitors. It initiates the operation by passing a visitor to the elements, which then process it.

Real-World Example Shopping Cart Discount Calculation

Problem

An online shopping cart has products like books, electronics, and groceries, each with different rules for discounts, taxes, and shipping. The system needs to calculate these dynamically without modifying the product classes, ensuring compliance with the Open-Closed Principle.

Solution

The Visitor Design Pattern allows us to add new operations (such as calculating discounts or taxes) without changing the product classes. By using this pattern, we can create visitors for each type of operation (e.g., DiscountVisitor, TaxVisitor) and apply them to the products without altering their structure.

Steps Involved

  1. Visitor Interface: Declare operations that can be performed on each product type.
  2. Concrete Visitor: Implement specific operations (e.g., calculating discounts or taxes) for each product type.
  3. Visitable (Element) Interface: Declare an accept method in each product class to accept a visitor.
  4. Concrete Visitable (Product Classes): Implement the accept method in each product (e.g., Book, Electronic, etc.) and pass itself to the visitor.
  5. Client: Use the visitor to calculate specific operations (e.g., applying discounts) for all the products in the shopping cart.
// C# Implementation of Visitor Pattern
using System;
// Step 1: Visitor Interface
public interface Visitor {
    void Visit(Book book);
    void Visit(Electronic electronic);
}

// Step 2: Concrete Visitor (Discount Calculation)
public class DiscountVisitor : Visitor {
    public void Visit(Book book) {
        Console.WriteLine("Book discount: 10%");
    }

    public void Visit(Electronic electronic) {
        Console.WriteLine("Electronic discount: 20%");
    }
}

// Step 3: Visitable (Element) Interface
public interface Visitable {
    void Accept(Visitor visitor);
}

// Step 4: Concrete Visitable Classes (Products)
public class Book : Visitable {
    public double Price { get; private set; }

    public Book(double price) {
        this.Price = price;
    }

    public void Accept(Visitor visitor) {
        visitor.Visit(this);
    }
}

public class Electronic : Visitable {
    public double Price { get; private set; }

    public Electronic(double price) {
        this.Price = price;
    }

    public void Accept(Visitor visitor) {
        visitor.Visit(this);
    }
}

// Step 5: Client
public class ShoppingCart {
    public static void Main(string[] args) {
        // Create products
        Book book = new Book(200);
        Electronic laptop = new Electronic(50000);

        // Create visitor for discount calculation
        DiscountVisitor discountVisitor = new DiscountVisitor();

        // Apply discount to the products
        book.Accept(discountVisitor);     
        laptop.Accept(discountVisitor);  
    }
}
# Python Implementation of Visitor Pattern

# Step 1: Visitor Interface
class Visitor:
    def visit_book(self, book):
        pass

    def visit_electronic(self, electronic):
        pass

# Step 2: Concrete Visitor (Discount Calculation)
class DiscountVisitor(Visitor):
    def visit_book(self, book):
        print("Book discount: 10%")

    def visit_electronic(self, electronic):
        print("Electronic discount: 20%")

# Step 3: Visitable (Element) Interface
class Visitable:
    def accept(self, visitor):
        pass

# Step 4: Concrete Visitable Classes (Products)
class Book(Visitable):
    def __init__(self, price):
        self.price = price

    def accept(self, visitor):
        visitor.visit_book(self)

class Electronic(Visitable):
    def __init__(self, price):
        self.price = price

    def accept(self, visitor):
        visitor.visit_electronic(self)

# Step 5: Client
if __name__ == "__main__":
    # Create products
    book = Book(200)
    laptop = Electronic(50000)

    # Create visitor for discount calculation
    discount_visitor = DiscountVisitor()

    # Apply discount to the products
    book.accept(discount_visitor)         
    laptop.accept(discount_visitor)       
// Java Implementation of Visitor Pattern
public class ShoppingCart {
    public static void main(String[] args) {
        // Create products
        Book book = new Book(200);
        Electronic laptop = new Electronic(50000);

        // Create visitor for discount calculation
        DiscountVisitor discountVisitor = new DiscountVisitor();

        // Apply discount to the products
        book.accept(discountVisitor);        
        laptop.accept(discountVisitor);      
    }
}

// Step 1: Visitor Interface
interface Visitor {
    void visit(Book book);
    void visit(Electronic electronic);
}

// Step 2: Concrete Visitor (Discount Calculation)
class DiscountVisitor implements Visitor {
    @Override
    public void visit(Book book) {
        System.out.println("Book discount: 10%");
    }

    @Override
    public void visit(Electronic electronic) {
        System.out.println("Electronic discount: 20%");
    }
}

// Step 3: Visitable (Element) Interface
interface Visitable {
    void accept(Visitor visitor);
}

// Step 4: Concrete Visitable Classes (Products)
class Book implements Visitable {
    private double price;

    public Book(double price) {
        this.price = price;
    }

    public double getPrice() {
        return price;
    }

    @Override
    public void accept(Visitor visitor) {
        visitor.visit(this);
    }
}

class Electronic implements Visitable {
    private double price;

    public Electronic(double price) {
        this.price = price;
    }

    public double getPrice() {
        return price;
    }

    @Override
    public void accept(Visitor visitor) {
        visitor.visit(this);
    }
}
// TypeScript Implementation of Visitor Pattern

// Step 1: Visitor Interface
interface Visitor {
    visitBook(book: Book): void;
    visitElectronic(electronic: Electronic): void;
}

// Step 2: Concrete Visitor (Discount Calculation)
class DiscountVisitor implements Visitor {
    visitBook(book: Book): void {
        console.log("Book discount: 10%");
    }

    visitElectronic(electronic: Electronic): void {
        console.log("Electronic discount: 20%");
    }
}

// Step 3: Visitable (Element) Interface
interface Visitable {
    accept(visitor: Visitor): void;
}

// Step 4: Concrete Visitable Classes (Products)
class Book implements Visitable {
    price: number;

    constructor(price: number) {
        this.price = price;
    }

    accept(visitor: Visitor): void {
        visitor.visitBook(this);
    }
}

class Electronic implements Visitable {
    price: number;

    constructor(price: number) {
        this.price = price;
    }

    accept(visitor: Visitor): void {
        visitor.visitElectronic(this);
    }
}

// Step 5: Client
let book = new Book(200);
let laptop = new Electronic(50000);

let discountVisitor = new DiscountVisitor();

book.accept(discountVisitor);    
laptop.accept(discountVisitor);  

Output

 Book discount: 10%
 Electronic discount: 20%

Explanation

  • Visitor defines methods to perform operations on different product types.
  • Concrete Visitor (DiscountVisitor) implements specific discount rules, applying a 10% discount for books and a 20% discount for electronics.
  • Visitable objects (like Book and Electronic) have an accept method to allow the visitor to process them.
  • Client (ShoppingCart) creates products, applies the DiscountVisitor to calculate discounts by invoking their accept methods.

Applications of Visitor Design Pattern

  • Compilers/Interpreters: Used to traverse abstract syntax trees (ASTs) to apply various operations like code generation, optimization, or analysis.
  • Object Structure Traversal: Useful for performing operations on hierarchical structures such as directories, file systems, or XML/JSON documents.
  • Graphics Systems: For handling various shapes or objects, where different operations like rendering, transformation, or collision detection need to be applied.
  • Financial Systems: For applying different taxation, discount, or fee rules across various financial instruments like loans, bonds, or accounts.

Relations with Other Design Patterns

  • Composite Pattern:Visitor can traverse a composite structure and apply operations on elements without altering the structure.
  • Iterator Pattern: Iterator provides sequential access; Visitor applies operations while traversing, often across more complex structures like trees.
  • Strategy Pattern: The visitordefines operations that can be swapped without modifying the objects, similar to how Strategy replaces algorithms dynamically.
  • Decorator Pattern:Both add functionality to objects, but the Decorator does so dynamically, while the Visitor adds new operations externally.
  • Interpreter Pattern: Visitor can be used in parsing structures, like Abstract Syntax Trees (AST), where different node types represent different rules in the grammar.
  • Double Dispatch: Visitor uses double dispatch, helping resolve method calls based on both the object type and visitor type.

When to use the Visitor Design Pattern

  • Adding new operations without modifying classes: When you need to add operations to a set of objects but don't want to change their structure.
  • Handling objects of different types: When you have a complex object hierarchy (like shapes or files) and need different behavior for each type.
  • Performing multiple unrelated operations: When you expect to apply several operations (like printing, calculating, or converting) on the same set of objects, without cluttering their classes.
  • Easily extending functionality:When new operations are frequently needed, it is easier to add them without altering the existing object structure.

When not to use Visitor Design Pattern

  • Frequent changes to object structure: If the object structure changes often, modifying all visitor classes can become cumbersome.
  • Simple hierarchy: If the object structure is simple, the overhead of implementing a visitor may be unnecessary.
  • Small number of operations: If only a few operations need to be performed, it's simpler to implement them directly in the classes.
  • When objects need to access private data: Visitor breaks encapsulation as it requires exposing internal details of objects to perform operations.
  • When you need only single dispatch: If polymorphism alone can solve the problem, Visitor’s double dispatch may be overkill.
Read More:
Understanding MVC, MVP, and MVVM Design Patterns
Top 50 Java Design Patterns Interview Questions and Answers
.Net Design Patterns Interview Questions, You Must Know!
Most Frequently Asked Software Architect Interview Questions and Answers
Conclusion

In conclusion, the Visitor design pattern allows adding new operations to objects without altering their structure, offering flexibility and separation of concerns. It’s ideal when new operations are needed frequently but should be avoided if the object structure changes often or access to private data is required.  To master design patterns, enroll in ScholarHat's Master Software Architecture and Design Certification Training.

FAQs

Q1. Can I use the Visitor Pattern with all types of object hierarchies?

The Visitor pattern is best suited for hierarchies where the structure is stable. It is less useful for flat structures or when frequent changes to the object hierarchy are required. 

Q2. What are the alternatives to the Visitor Pattern?

Alternatives include using the Strategy Pattern for algorithm variations, Command Pattern for encapsulating requests, or directly adding methods to the object classes if the operations are relatively few and unlikely to change. 

Q3. Can the Visitor Pattern be used with multiple visitors?

Yes, you can have multiple visitors, each implementing different operations. This allows for adding diverse functionalities while keeping the object classes unchanged. 

Q4. How do you test code that uses the Visitor Pattern?

Testing involves creating mock or concrete visitors and verifying that the expected operations are applied correctly to different types of elements. Unit tests can be designed to ensure that visitors behave correctly when interacting with various elements. 
Share Article

Live Classes Schedule

Our learn-by-building-project method enables you to build practical/coding experience that sticks. 95% of our learners say they have confidence and remember more when they learn by building real world projects.
ASP.NET Core Certification TrainingOct 26SAT, SUN
Filling Fast
09:30AM to 11:30AM (IST)
Get Details

Can't find convenient schedule? Let us know

About Author
Shailendra Chauhan (Microsoft MVP, Founder & CEO at Scholarhat by DotNetTricks)

Shailendra Chauhan is the Founder and CEO at ScholarHat by DotNetTricks which is a brand when it comes to e-Learning. He provides training and consultation over an array of technologies like Cloud, .NET, Angular, React, Node, Microservices, Containers and Mobile Apps development. He has been awarded Microsoft MVP 9th time in a row (2016-2024). He has changed many lives with his writings and unique training programs. He has a number of most sought-after books to his name which has helped job aspirants in cracking tough interviews with ease.
Accept cookies & close this