- It represents an operation to be performed on the elements of an object structure.
- Visitor lets you define a new operation without changing the classes of the elements on which it operates.
- You may find yourself in a situation where you need to make a functional change to a collection of classes but you do not have control over the classes in the hierarchy.
- The intent of the Visitor design pattern is to define a new operation for a collection of classes (i.e. the classes being visited) without changing the hierarchy itself.
- The new logic lives in a separate class, the Visitor.
- The tricky aspect of the Visitor pattern is that the original developer of the classes in the collection must have anticipated functional adjustments that may occur in the future by including methods that accept a Visitor class and let it define new operations.
- Avoid using this pattern as it adds complexity and creates fragile code that goes against generally accepted best practices in object-oriented design.
- When deciding to use this pattern it is usually best to carefully weight it against alternative approaches, such as inheritance or delegation.
Participants
Visitor
(IPerk)
- Declares a Visit operation for each class of
ConcreteElement
in the object structure.
- The operation's identifies the class that sends the Visit request to the visitor.
- That lets the visitor determine the concrete class of the element being visited.
- Then the visitor can access the elements directly through its particular interface.
ConcreteVisitor
(IncomePerk, VacationPerk)
- Implements each operation declared by Visitor.
- Each operation implements a fragment of the algorithm defined for the corresponding class or object in the structure.
- ConcreteVisitor provides the context for the algorithm and stores its local state.
- This state often accumulates results during the traversal of the structure.
Element
(IEmployee)
- Defines Accept operation that takes a visitor as an argument.
ConcreteElement
(Employee)
- Implements an Accept operation that takes a visitor as an argument.
ObjectStructure
(Employees)
- Can enumerate its elements.
- May provide a high-level interface to allow the visitor to visit its elements.
- May either be a Composite (pattern) or a collection such as a list or a set.
var employees = new Employees();
var employee1 = new Clerk
{
Name = "Hank",
Income = 25000.0,
VacationDays = 14
};
var employee2 = new Director
{
Name = "Elly",
Income = 35000.0,
VacationDays = 16
};
var employee3 = new President
{
Name = "Dick",
Income = 45000.0,
VacationDays = 21
};
employees.Hire(employee1);
employees.Hire(employee2);
employees.Hire(employee3);
employees.AddPerk(new IncomePerk());
employees.AddPerk(new VacationPerk());
public interface IPerk
{
void Allocate(IEmployee employee);
}
public interface IEmployee
{
string Name { get; set; }
double Income { get; set; }
int VacationDays { get; set; }
void Accept(IPerk perk);
}
public class Employee : IEmployee
{
public string Name { get; set; }
public double Income { get; set; }
public int VacationDays { get; set; }
public void Accept(IPerk perk) => perk.Allocate(this);
}
public class IncomePerk : IPerk
{
public void Allocate(IEmployee employee) => employee.Income *= 1.10; // Provide 10% pay raise
}
public class VacationPerk : IPerk
{
public void Allocate(IEmployee employee) => employee.VacationDays += 3; // Provide 3 extra vacation days.
}
public class Employees
{
private List<Employee> _Employees = new List<Employee>();
public void Hire(Employee employee) => _Employees.Add(employee);
public void Terminate(Employee employee) => _Employees.Remove(employee);
public void AddPerk(IPerk perk)
{
foreach (var employee in _Employees)
employee.Accept(perk);
}
}
public class Clerk : Employee { }
public class Director : Employee { }
public class President : Employee { }
Rules of Thumb
- The abstract syntax tree of Interpreter is a Composite (therefore Iterator and Visitor are also applicable).
- Iterator can traverse a Composite. Visitor can apply an operation over a Composite.
- The Visitor pattern is like a more powerful Command pattern because the visitor may initiate whatever is appropriate for the kind of object it encounters.
- The Visitor pattern is the classic technique for recovering lost type information without resorting to dynamic casts.