Visitor
Add new operations to a set of classes without changing them, by moving each operation into its own visitor.
- Behavioral
- A payroll for full-time and part-time staff
- Java code
The idea
Say you have a stable set of classes, such as kinds of employee, and you keep needing new operations on them: work out pay, export to a spreadsheet, print a report. Adding each operation as a method to every class spreads unrelated code across all of them, and means editing them every time.
The Visitor pattern moves each operation into its own class, a visitor. The visitor has one visit method for each class it can work on. Each of those classes gets a single accept(visitor) method that calls visitor.visit(this). Inside accept, this has the exact type of the class, so Java picks the matching visit method.
This two-step call is known as double dispatch: the code that runs depends on both the type of the visitor and the type of the element. A new operation is now a new visitor class, and the element classes don't change.
When to use it
- You need many unrelated operations on a set of classes, and don't want to fill those classes with them.
- The set of classes rarely changes, but new operations are added often.
- An operation needs different logic for each class, and you want to avoid
instanceofchecks. - You want to collect results across a structure of objects, such as totals over a list or a tree.
Pros and cons
Pros
- Adding a new operation is easy: write a new visitor, without touching the element classes.
- It separates concerns: each operation lives in one class instead of being spread across many.
- A visitor can keep state as it goes, such as a running total.
Cons
- It adds classes to the system: one for every operation.
- Adding a new element class is hard, because every visitor needs a new
visitmethod. - Visitors often need public getters on the elements to do their work, which weakens encapsulation.
The example
The example is a payroll. An organisation has full-time and part-time employees, and their pay is worked out in different ways. That calculation lives in a visitor, not in the employee classes.
Employee: the element interface, with a singleaccept(visitor)method.EmployeeVisitor: the visitor interface, with onevisitmethod for each kind of employee.FullTimeEmployeeandPartTimeEmployee: concrete elements. Eachaccept()callsvisitor.visit(this).SalaryCalculator: the concrete visitor. It adds a full-time employee's salary to the total. For a part-time employee, it adds the hourly rate times 20 × 8, that is 160 hours.Organization: the object structure. It holds the employees, passes oneSalaryCalculatorto each of them, and prints the total.Demo: adds a full-time employee on 10,000 and a part-time one at 30 an hour, then asks for the total: 10,000 plus 30 × 160.
The code
public interface Employee {
void accept(EmployeeVisitor visitor);
}
public interface EmployeeVisitor {
void visit(FullTimeEmployee employee);
void visit(PartTimeEmployee employee);
}
public class FullTimeEmployee implements Employee {
private final String name;
private final 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(EmployeeVisitor visitor) {
visitor.visit(this);
}
}
public class PartTimeEmployee implements Employee {
private final String name;
private final double hourlyRate;
public PartTimeEmployee(String name, double hourlyRate) {
this.name = name;
this.hourlyRate = hourlyRate;
}
public String getName() {
return name;
}
public double getHourlyRate() {
return hourlyRate;
}
@Override
public void accept(EmployeeVisitor visitor) {
visitor.visit(this);
}
}
public class SalaryCalculator implements EmployeeVisitor {
private double totalSalary;
@Override
public void visit(FullTimeEmployee employee) {
totalSalary += employee.getSalary();
}
@Override
public void visit(PartTimeEmployee employee) {
totalSalary += employee.getHourlyRate() * 20 * 8;
}
public double getTotalSalary() {
return totalSalary;
}
}
import java.util.ArrayList;
import java.util.List;
public class Organization {
List<Employee> employees;
public Organization() {
employees = new ArrayList<>();
}
public void addEmployee(Employee employee) {
employees.add(employee);
}
public void calculateSalary() {
SalaryCalculator calculator = new SalaryCalculator();
for (Employee employee : employees) {
employee.accept(calculator);
}
System.out.println("Total salary: " + calculator.getTotalSalary());
}
}
public class Demo {
public static void main(String[] args) {
Organization company = new Organization();
company.addEmployee(new FullTimeEmployee("Haytham", 10000));
company.addEmployee(new PartTimeEmployee("Hamza", 30));
company.calculateSalary();
}
}
Running Demo prints:
Total salary: 14800.0