BlackWasp
Design Patterns
.NET 2.0+

Visitor Design Pattern

The visitor pattern is a design pattern that separates a set of structured data from the functionality that may be performed upon it. This promotes loose coupling and enables additional operations to be added without modifying the data classes.

What is the Visitor Pattern?

The visitor pattern is a Gang of Four design pattern. This is a behavioural pattern as it defines a manner for controlling communication between classes or entities. The visitor pattern is used to separate a relatively complex set of structured data classes from the functionality that may be performed upon the data that they hold. This allows the creation of a data model with limited internal functionality and a set of visitors that perform operations upon the data. The pattern specifically allows each of the elements of a data structure to be visited in turn without knowing the details of the structure beforehand.

The key benefit of separating the data model from the algorithms that may be applied to it is the ability to add new operations easily. The classes of the data structure are initially created with the inclusion of a method that may be called by a visitor object. This method performs a callback to the visitor, passing itself to the visitor's method as a parameter. The visitor can then perform operations upon the data object. To add a new operation, a new visitor class is created with the appropriate callback method. The data classes need no further modification.

A second benefit of the design pattern is that a single visitor object is used to visit all elements of the data structure. The visitor object can maintain state between calls to individual data objects.

An example of the use of the visitor design pattern could be used within a personnel system. The data structure could define a hierarchy of managers and employees, each with a salary property. This system could include two visitor algorithms. The first would traverse the hierarchy and generate monthly salary payments. The second could apply a standard pay increase to each employee.

Implementing the Visitor Pattern

Visitor Design Pattern UML

The UML class diagram above shows an implementation of the visitor design pattern. The items in the diagram are described below:

  • Client. The Client class is a consumer of the classes of the visitor design pattern. It has access to the data structure objects and can instruct them to accept a Visitor to perform the appropriate processing.
  • ObjectStructure. The ObjectStructure class holds all of the elements of the data structure that can be used by visitors. The elements may be held in a simple collection or a more complex structure. The class includes a method, in the above diagram named "Accept", that can be called by the client with a Visitor object passed as a parameter. The ObjectStructure class then enumerates the contained elements, calling the Accept method of each and passing the provided Visitor. This allows each element to be processed without the client requiring any knowledge of the elements beforehand.
  • ElementBase. This abstract class is the base class for all element objects. It defines the accept method that each element must implement in order to be visited.
  • ConcreteElement A/B. Concrete element objects are those that hold real information in the data structure. To enable their use with the visitor design pattern, each must implement the Accept method. Usually the Accept method simply performs a callback to the visitor object. When the callback is made, the element object is passed to the visitor's Visit method so that it may execute an algorithm using the element's data.
  • VisitorBase. This class is the abstract base class for all concrete visitors. It defines a method, in the above diagram named "Visit", that can be called by element objects during the callback process. The method is generally overloaded with a version that is capable of processing any of the concrete element types.
  • ConcreteVisitor A/B. The concrete visitor classes contain the operations that are applied to the concrete element objects. They implement the various overloaded Visit methods defined in the VisitorBase class.

The following shows the basic code of the visitor design pattern implemented using C#. To simplify the example the code uses C# 3.0 automatically implemented property syntax to define properties. For earlier versions of the language you should use full property declarations with backing variables.

public class ObjectStructure
{
    public List<ElementBase> Elements { get; private set; }

    public ObjectStructure()
    {
        Elements = new List<ElementBase>();
    }

    public void Accept(VisitorBase visitor)
    {
        foreach (ElementBase element in Elements)
        {
            element.Accept(visitor);
        }
    }
}


public abstract class ElementBase
{
    public abstract void Accept(VisitorBase visitor);
}


public class ConcreteElementA : ElementBase
{
    public override void Accept(VisitorBase visitor)
    {
        visitor.Visit(this);
    }

    public string Name { get; set; }
}


public class ConcreteElementB : ElementBase
{
    public override void Accept(VisitorBase visitor)
    {
        visitor.Visit(this);
    }

    public string Title { get; set; }
}


public abstract class VisitorBase
{
    public abstract void Visit(ConcreteElementA element);

    public abstract void Visit(ConcreteElementB element);
}


public class ConcreteVisitorA : VisitorBase
{
    public override void Visit(ConcreteElementA element)
    {
        Console.WriteLine("VisitorA visited ElementA : {0}", element.Name);
    }

    public override void Visit(ConcreteElementB element)
    {
        Console.WriteLine("VisitorA visited ElementB : {0}", element.Title);
    }
}


public class ConcreteVisitorB : VisitorBase
{
    public override void Visit(ConcreteElementA element)
    {
        Console.WriteLine("VisitorB visited ElementA : {0}", element.Name);
    }

    public override void Visit(ConcreteElementB element)
    {
        Console.WriteLine("VisitorB visited ElementB : {0}", element.Title);
    }
}

Example Visitor

Earlier in the article I described an example use of the visitor design pattern. In this example, the data structure holds the employees of a business, each as a single element object with properties to hold a name and monthly salary. The objects are held in a hierarchy with a single manager at the top of the tree and subordinates beneath. Each subordinate can be another manager, with their own subordinates, or a worker with none.

To create the classes for such an object structure, create a new console application project and add the code below. This defines an OrganisationalStructure class that holds the key employee. Further employees can be defined as subordinates to generate the hierarchy. To support the visitor design pattern, the employee classes include an Accept method as described previously. For managers, the accept method performs a callback to the visitor object and calls Accept for all subordinates. This ensures that a visitor will process every employee in the business.

public class OrganisationalStructure
{
    public EmployeeBase Employee { get; set; }

    public OrganisationalStructure(EmployeeBase firstEmployee)
    {
        Employee = firstEmployee;
    }

    public void Accept(VisitorBase visitor)
    {
        Employee.Accept(visitor);
    }
}


public abstract class EmployeeBase
{
    public abstract void Accept(VisitorBase visitor);

    public string Name { get; set; }

    public double MonthlySalary { get; set; }
}


public class Worker : EmployeeBase
{
    public override void Accept(VisitorBase visitor)
    {
        visitor.Visit(this);
    }
}


public class Manager : EmployeeBase
{
    public Manager()
    {
        Subordinates = new List<EmployeeBase>();
    }

    public List<EmployeeBase> Subordinates { get; private set; }

    public override void Accept(VisitorBase visitor)
    {
        visitor.Visit(this);

        foreach (EmployeeBase subordinate in Subordinates)
        {
            subordinate.Accept(visitor);
        }
    }
}

We will create two visitor classes that can be used with the object structure. The first of these will be used by the payroll software to pay the employees on a monthly basis. The code below defines the visitor base class and the payroll visitor functionality. To simulate payments, the PayrollVisitor class outputs information to the console.

public abstract class VisitorBase
{
    public abstract void Visit(Worker employee);

    public abstract void Visit(Manager employee);
}


public class PayrollVisitor : VisitorBase
{
    public override void Visit(Worker employee)
    {
        Console.WriteLine("{0} paid {1}.", employee.Name, employee.MonthlySalary);
    }

    public override void Visit(Manager employee)
    {
        Console.WriteLine("{0} paid {1} + Bonus."
            , employee.Name, employee.MonthlySalary);
    }
}

The second visitor will be used to apply a standard pay rise to all employees. The visitor includes a custom constructor that accepts a multiplier. This multiplier will be used to determine the increase in pay that each employee will receive. In the Visit method, the increase is calculated and the employee's monthly salary is changed accordingly. The increase is also added to a property that records the total increase in salary for all employees processed.

public class PayriseVisitor : VisitorBase
{
    double _multiplier;

    public double TotalIncrease { get; private set; }

    public PayriseVisitor(double multiplier)
    {
        _multiplier = multiplier;
        TotalIncrease = 0;
    }

    public override void Visit(Worker employee)
    {
        double increase = employee.MonthlySalary * _multiplier;
        employee.MonthlySalary += increase;
        TotalIncrease += increase;
        Console.WriteLine("{0} salary increased by {1}.", employee.Name, increase);
    }

    public override void Visit(Manager employee)
    {
        double increase = employee.MonthlySalary * _multiplier;
        employee.MonthlySalary += increase;
        TotalIncrease += increase;
        Console.WriteLine("{0} salary increased by {1}.", employee.Name, increase);
    }
}

Testing the Visitor

To test the example we can add some code to the Main method of the console application. The following code creates five employees in a three-level hierarchy. Once the structure is created, a payroll visitor is applied to the organisational structure to output the salary details for each employee. Next, a pay rise visitor increases the monthly salary for each employee by 5%. The payroll visitor is then used again to display the new salaries. Finally, the total increase in salary is displayed.

Manager bob = new Manager();
bob.Name = "Bob";
bob.MonthlySalary = 5000;

Manager sue = new Manager();
sue.Name = "Sue";
sue.MonthlySalary = 4000;

Worker jim = new Worker();
jim.Name = "Jim";
jim.MonthlySalary = 2000;

Worker tom = new Worker();
tom.Name = "Tom";
tom.MonthlySalary = 1800;

Worker mel = new Worker();
mel.Name = "Mel";
mel.MonthlySalary = 1900;

bob.Subordinates.Add(sue);
bob.Subordinates.Add(jim);
sue.Subordinates.Add(tom);
sue.Subordinates.Add(mel);

OrganisationalStructure org = new OrganisationalStructure(bob);

PayrollVisitor payroll = new PayrollVisitor();
PayriseVisitor payrise = new PayriseVisitor(0.05);

org.Accept(payroll);
org.Accept(payrise);
org.Accept(payroll);

Console.WriteLine("Total pay increase = {0}.", payrise.TotalIncrease);

/* OUTPUT

Bob paid 5000 + Bonus.
Sue paid 4000 + Bonus.
Tom paid 1800.
Mel paid 1900.
Jim paid 2000.
Bob salary increased by 250.
Sue salary increased by 200.
Tom salary increased by 90.
Mel salary increased by 95.
Jim salary increased by 100.
Bob paid 5250 + Bonus.
Sue paid 4200 + Bonus.
Tom paid 1890.
Mel paid 1995.
Jim paid 2100.
Total pay increase = 735.

*/
Link to this Page21 August 2009
TwitterTwitter RSS Feed RSS