Visitor Design Pattern

Anubhav Gupta
4 min readOct 24, 2024

--

The Visitor Design Pattern is a behavioral design pattern that allows adding further operations to objects without modifying their structure. It enables a clean way to separate algorithms from the objects on which they operate. By applying this pattern, you can define new operations on a set of objects by “visiting” them and performing tasks, without changing the classes of the elements being operated on.

Key Concepts

  1. Visitor Interface: Declares a visit method for each type of element in the object structure that the visitor can process.
  2. Concrete Visitor: Implements the visit methods, defining actions for each type of element.
  3. Element Interface: Declares an accept method that takes a visitor as an argument.
  4. Concrete Element: Implements the accept method to pass the element to the visitor.
  5. Object Structure: Contains the elements and allows a visitor to traverse through them, often implementing a collection of elements.
// Visitor Interface
interface Visitor {
void visit(Book book);
void visit(Fruit fruit);
}

// Concrete Visitor
class PriceVisitor implements Visitor {
public void visit(Book book) {
System.out.println("Book Price: " + book.getPrice());
}

public void visit(Fruit fruit) {
System.out.println("Fruit Price: " + fruit.getPrice());
}
}

// Element Interface
interface Item {
void accept(Visitor visitor);
}

// Concrete Element: Book
class Book implements Item {
private int price;

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

public int getPrice() {
return price;
}

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

// Concrete Element: Fruit
class Fruit implements Item {
private int price;

public Fruit(int price) {
this.price = price;
}

public int getPrice() {
return price;
}

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

// Client
public class VisitorPatternDemo {
public static void main(String[] args) {
Item[] items = new Item[] { new Book(100), new Fruit(50) };
Visitor visitor = new PriceVisitor();

for (Item item : items) {
item.accept(visitor); // Visitor processes each element
}
}
}

Advantages

  • Single Responsibility Principle: Separates the algorithms from the object structure.
  • Open/Closed Principle: You can add new operations by creating new visitors without modifying existing element classes.

Disadvantages

  • Double Dispatch: The pattern requires double dispatch (calling the accept method followed by the visit method), which can be less efficient and harder to understand in some cases.
  • Complexity: For large systems with many element types and visitors, managing all the visit methods can become complex.

Use Cases

  • When you need to perform multiple operations on objects without modifying their classes.
  • When an object structure contains different types of objects, and you need to execute different logic depending on the object’s type.

Use Case of Visitor Design Pattern:

Scenario: Tax Calculation System for Different Types of Employees

Imagine a system where you have different types of employees in an organization, such as Full-time employees, Contract employees, and Interns. The tax rules for each type of employee vary based on their employment status. You may want to calculate tax or generate reports for each employee type, but modifying their classes for every new operation would violate the Single Responsibility Principle.

// Visitor Interface
interface EmployeeVisitor {
void visit(FullTimeEmployee employee);
void visit(ContractEmployee employee);
void visit(Intern employee);
}

// Concrete Visitor (Tax Calculation Visitor)
class TaxVisitor implements EmployeeVisitor {
@Override
public void visit(FullTimeEmployee employee) {
System.out.println("Calculating tax for full-time employee.");
}

@Override
public void visit(ContractEmployee employee) {
System.out.println("Calculating tax for contract employee.");
}

@Override
public void visit(Intern employee) {
System.out.println("Interns are exempt from tax.");
}
}

// Employee Interface (Element)
interface Employee {
void accept(EmployeeVisitor visitor);
}

// Concrete Elements
class FullTimeEmployee implements Employee {
@Override
public void accept(EmployeeVisitor visitor) {
visitor.visit(this);
}
}

class ContractEmployee implements Employee {
@Override
public void accept(EmployeeVisitor visitor) {
visitor.visit(this);
}
}

class Intern implements Employee {
@Override
public void accept(EmployeeVisitor visitor) {
visitor.visit(this);
}
}

Adding New Operations:

With the Visitor pattern, if you want to add new operations (e.g., PerformanceReportVisitor to generate employee performance reports), you don’t have to modify the existing employee classes. You simply create a new visitor and implement the necessary methods for each type of employee.

// Concrete Visitor (Performance Report Visitor)
class PerformanceReportVisitor implements EmployeeVisitor {
@Override
public void visit(FullTimeEmployee employee) {
System.out.println("Generating performance report for full-time employee.");
}

@Override
public void visit(ContractEmployee employee) {
System.out.println("Generating performance report for contract employee.");
}

@Override
public void visit(Intern employee) {
System.out.println("Generating performance report for intern.");
}
}

Client Code:

public class VisitorPatternDemo {
public static void main(String[] args) {
Employee fullTime = new FullTimeEmployee();
Employee contract = new ContractEmployee();
Employee intern = new Intern();

// Tax Visitor
EmployeeVisitor taxVisitor = new TaxVisitor();
fullTime.accept(taxVisitor);
contract.accept(taxVisitor);
intern.accept(taxVisitor);

// Performance Report Visitor
EmployeeVisitor performanceVisitor = new PerformanceReportVisitor();
fullTime.accept(performanceVisitor);
contract.accept(performanceVisitor);
intern.accept(performanceVisitor);
}
}

Real-world Use Cases:

  • Compilers: The Visitor pattern is often used in compilers to traverse the syntax tree and apply different operations (e.g., type checking, code generation).
  • UI Frameworks: For traversing different UI components (e.g., buttons, labels, text fields) and applying operations like rendering or event handling.
  • Document Structure: In document editors like Word processors, you can apply operations (like spell-checking, printing, formatting) to different parts of a document.

The Visitor pattern is a great fit when you have a complex object structure that may change, but the operations performed on the objects may vary over time.

--

--

Anubhav Gupta
Anubhav Gupta

Written by Anubhav Gupta

Machine Learning , Computer Vision and Deep Learning Enthusiast

No responses yet