The Visitor pattern lets us operate on objects by representing that operation as an object unto itself. We can then operate on said objects without changing the definitions or functionality of those objects.
This pattern is particularly useful when, for one reason or another, we cannot modify or refactor existing classes but need to change their behavior.
This is part of a series of posts demonstrating software design patterns using C# and .NET. The patterns are taken from the book Design Patterns by the Gang of Four. Check out the other posts in the series!
The Rundown
- Type: Behavioral
- Useful? 1/5 (Rarely)
- Good For: Operating on objects without changing their classes.
- Example Code: On GitHub
The Participants
- The Visitor declares an operation for each of ConcreteElement in the object structure.
- The ConcreteVisitor implements each operation defined by the Visitor. Each operation implements a fragment of the algorithm needed for that object.
- The Element defines an Accept operation which takes a Visitor as an argument.
- The ConcreteElement implements the Accept operation defined by the Element.
- The ObjectStructure can enumerate its elements and may provide a high-level interface to allow the Visitor to visit its elements.
A Delicious Example
To model this pattern, let's talk about a very successful restaurant and the employees who work there.
In this scenario, our restaurant has been, to put it lightly, killing it. They're full every day, customers rave about the menu, critics love the decor and the staff; in short, they're the best restaurant in the city.
Upper management has decided that it's time to reward our hard-working employees by giving them raises and extra time off. Problem is, the classes which represent our employees in our payroll system are already created and, for whatever reason, cannot be changed.
/// <summary>
/// The Element abstract class. All this does
/// is define an Accept operation, which needs
/// to be implemented by any class that can be visited.
/// </summary>
abstract class Element
{
public abstract void Accept(IVisitor visitor);
}
/// <summary>
/// The ConcreteElement class, which implements
/// all operations defined by the Element.
/// </summary>
class Employee : Element
{
public string Name { get; set; }
public double AnnualSalary { get; set; }
public int PaidTimeOffDays { get; set; }
public Employee(string name, double annualSalary, int paidTimeOffDays)
{
Name = name;
AnnualSalary = annualSalary;
PaidTimeOffDays = paidTimeOffDays;
}
public override void Accept(IVisitor visitor)
{
visitor.Visit(this);
}
}
Above are our Element and ConcreteElement participants, representing employees of our restaurant. We need to implement a Visitor which will visit these employee records and modify their salary and paid time off accordingly.
First, let's get our interface IVisitor
defined (which is also our Visitor participant):
/// <summary>
/// The Visitor interface, which declares
/// a Visit operation for each ConcreteVisitor to implement.
/// </summary>
interface IVisitor
{
void Visit(Element element);
}
Now we need our ConcreteVisitor participants, one for each detail about the employee records that we want to change.
/// <summary>
/// A Concrete Visitor class.
/// </summary>
class IncomeVisitor : IVisitor
{
public void Visit(Element element)
{
Employee employee = element as Employee;
// We've had a great year, so 10% pay raises for everyone!
employee.AnnualSalary *= 1.10;
Console.WriteLine("{0} {1}'s new income: {2:C}", employee.GetType().Name, employee.Name, employee.AnnualSalary);
}
}
/// <summary>
/// A Concrete Visitor class
/// </summary>
class PaidTimeOffVisitor : IVisitor
{
public void Visit(Element element)
{
Employee employee = element as Employee;
// And because you all helped have such a great year,
// all my employees get three extra paid time off days each!
employee.PaidTimeOffDays += 3;
Console.WriteLine("{0} {1}'s new vacation days: {2}", employee.GetType().Name, employee.Name, employee.PaidTimeOffDays);
}
}
Finally, we need classes to represent all of our employees as a group and individually. The aggregate collection of employees is our ObjectStructure participant:
/// <summary>
/// The Object Structure class, which is a collection of Concrete Elements.
/// This could be implemented using another pattern such as Composite.
/// </summary>
class Employees
{
private List<Employee> _employees = new List<Employee>();
public void Attach(Employee employee)
{
_employees.Add(employee);
}
public void Detach(Employee employee)
{
_employees.Remove(employee);
}
public void Accept(IVisitor visitor)
{
foreach (Employee e in _employees)
{
e.Accept(visitor);
}
Console.WriteLine();
}
}
class LineCook : Employee
{
public LineCook() : base("Dmitri", 32000, 7) { }
}
class HeadChef : Employee
{
public HeadChef() : base("Jackson", 69015, 21) { }
}
class GeneralManager : Employee
{
public GeneralManager() : base("Amanda", 78000, 24) { }
}
When we run the app, we will create a new collection of employees and send visitors to modify their salary and paid time off records. It looks like this:
static void Main(string[] args)
{
// Who are my employees?
Employees e = new Employees();
e.Attach(new LineCook());
e.Attach(new HeadChef());
e.Attach(new GeneralManager());
// Employees are visited, giving them 10% raises
// and 3 extra paid time off days.
e.Accept(new IncomeVisitor());
e.Accept(new PaidTimeOffVisitor());
Console.ReadKey();
}
Running the sample app produces the following output:
In short, we created two visitors (IncomeVisitor
and PaidTimeOffVisitor
) which visited each Employee
and modified their internal states before vanishing like a thief in the night. All of our employees are surely happy with the new money and time we've given them.
Will I Ever Use This Pattern?
Probably not, at least not for simple projects. To be honest, I'm tempted to think of this pattern as being the Burglar pattern rather than the Visitor pattern, since it consists of some heretofore unknown instance of an object showing up, breaking in, rearranging things, and leaving.
Further, it appears that more recent versions of C# might actually negate the need to use this pattern in certain scenarios in the first place. Again, I don't have a lot of first-hand experience with this pattern, but if you think it will help you and your projects, might as well give it a go.
Summary
The Visitor pattern allows us to modify existing instances of objects without modifying the class they are a part of. All those instances need to do is accept a Visitor object and process its contents. That said, this pattern (IMO) provides more complexity than value and shouldn't be used extensively.
Happy Coding!