Visitor Design Pattern in Java
1. Introduction
The Visitor Pattern lets you separate algorithms from the objects on which they operate.
It allows you to add new operations to existing object structures without modifying them.
2. Real-World Analogy
Think of a Tax Calculation System:
- A company has different kinds of employees (Full-time, Part-time, Interns).
- You want to perform different calculations (tax, benefits, bonuses) without changing the employee classes.
- Visitor Pattern allows you to add these calculations externally without modifying existing employee classes.
3. Structure of Visitor Pattern
- Visitor Interface → Declares visit methods for each element type.
- Concrete Visitors → Implement the visitor interface with different operations.
- Element Interface → Defines accept method to take a visitor.
- Concrete Elements → Implement accept method and call the visitor.
- Object Structure → Collection of elements to be visited.
4. Custom Implementation Example
Let’s create a Tax Calculation System for different employee types.
Step 1 – Visitor Interface
public interface Visitor {
void visit(FullTimeEmployee employee);
void visit(PartTimeEmployee employee);
}
Step 2 – Element Interface
public interface Employee {
void accept(Visitor visitor);
}
Step 3 – Concrete Elements
Full-Time Employee
public class FullTimeEmployee implements Employee {
private String name;
private double salary;
public FullTimeEmployee(String name, double salary) {
this.name = name;
this.salary = salary;
}
public String getName() { return name; }
public double getSalary() { return salary; }
@Override
public void accept(Visitor visitor) {
visitor.visit(this);
}
}
Part-Time Employee
public class PartTimeEmployee implements Employee {
private String name;
private double hourlyWage;
private int hoursWorked;
public PartTimeEmployee(String name, double hourlyWage, int hoursWorked) {
this.name = name;
this.hourlyWage = hourlyWage;
this.hoursWorked = hoursWorked;
}
public String getName() { return name; }
public double getHourlyWage() { return hourlyWage; }
public int getHoursWorked() { return hoursWorked; }
@Override
public void accept(Visitor visitor) {
visitor.visit(this);
}
}
Step 4 – Concrete Visitor
public class TaxVisitor implements Visitor {
@Override
public void visit(FullTimeEmployee employee) {
double tax = employee.getSalary() * 0.2;
System.out.println(employee.getName() + " (Full-time) tax: $" + tax);
}
@Override
public void visit(PartTimeEmployee employee) {
double tax = employee.getHourlyWage() * employee.getHoursWorked() * 0.1;
System.out.println(employee.getName() + " (Part-time) tax: $" + tax);
}
}
Step 5 – Object Structure
import java.util.ArrayList;
import java.util.List;
public class EmployeeCollection {
private List employees = new ArrayList<>();
public void addEmployee(Employee employee) {
employees.add(employee);
}
public void accept(Visitor visitor) {
for (Employee employee : employees) {
employee.accept(visitor);
}
}
}
Step 6 – Client Code
public class VisitorPatternDemo {
public static void main(String[] args) {
EmployeeCollection employees = new EmployeeCollection();
employees.addEmployee(new FullTimeEmployee("Alice", 5000));
employees.addEmployee(new PartTimeEmployee("Bob", 20, 80));
Visitor taxVisitor = new TaxVisitor();
employees.accept(taxVisitor);
}
}
5. Output
Alice (Full-time) tax: $1000.0
Bob (Part-time) tax: $160.0
6. Advantages
- Adds new operations without changing existing classes.
- Encourages single responsibility — separates data structure from operations.
- Good for performing unrelated operations over a complex object structure.
7. Real-World Use Cases
- Tax calculation systems.
- Code analysis tools.
- Reporting and exporting data in multiple formats.
- Compilers / AST traversal (Abstract Syntax Trees).