Visitor Pattern

As described in the original GoF book, Visitor Pattern lets you define a new operation without changing the classes of the elements on which it operates. Visitor Pattern is using an OOP technique called Double Dispatch.

Problem

Let's say you want to generate this kind of report for employees:

Emp#

Name

QTD-hours

QTD-pay

1

Sapphira Janna

42

$20,000

2

Maks Chi

90

$22,000

...

public abstract class Employee 
{
  public abstract String reportQtdHoursAndPay();
}

public class HourlyEmployee : Employee
{
  public String reportQtdHoursAndPay() 
  {
    //generate the line for this hourly employee
  }
}

public class SalariedEmployee : Employee
{
    public String reportQtdHoursAndPay() 
    { 
        //do nothing
    }
}

When generating the report, you iterate the employees and call the reportQtdHoursAndPay() method. The problem with this approach is that you couple the format of the report with the Employee object. This violates the Single Responsibility Principle and causes Employee to be changed every time the format of the report changes.

To resolve this, we can extract the report-generating operation from the Employee class. It will handle multiple types of Employee:

public interface IReport
{
    string Generate(HourlyEmployee employee);
    string Generate(SalariedEmployee employee);
}

public class QtdHoursAndPayReport : IReport 
{
    // compiler cannot resolve this method
    public string Generate(HourlyEmployee he)
    {
        //generate the line for this hourly employee
    }

    // compiler cannot resolve this method
    public string Generate(SalariedEmployee se)
    {
        // do nothing
    }
}

Employee employee = new HourlyEmployee();
IReport report = new QtdHoursAndPayReport();
report.Generate(employee);

Single Dispatch vs Multiple Dispatch

According to OOP principles, Generate method call represents a message sent to an object, and report object represents a receiver. The receiver receives a message and needs somehow to react to it.

When dealing with polymorphic(virtual) method calls, the method resolution logic depends on the runtime type of the receiver and the compile-time types of the method arguments.

We can describe any object method call as a regular function invocation with one more argument – this, pointing to the receiving object itself.

Since most programming languages depend only on the runtime type of this, they are classified as Single Dispatch languages.

Based on this, when looking at the example we know that method resolution logic will know that it has to call Generate(e) on the QtdHoursAndPayReport object, since QtdHoursAndPayReport is the runtime type of the report object.

But what if we want to use polymorphism based on the runtime types of the arguments? In the example, there are two methods named Generate that handle HourlyEmployee and SalariedEmployee, respectively. Based on the runtime type of the employee object, we would like the method call to be dispatched to the corresponding Generate method.

If you try this, the compiler will complain that it cannot properly resolve the method. This is because the C# supports virtual calls only on the receiver, not on the method arguments. Resolving what method to execute based on the runtime types of the arguments (not only this) is called Multiple Dispatch.

In essence, the Visitor Pattern is a way to emulate Multiple Dispatch (or more accurately – Double Dispatch). This is accomplished by using a chain of Single Dispatch calls.

Implementation

To overcome these limitations, the Visitor Pattern can be used as follows:

public abstract class Employee
{
    public abstract void Accept(IEmployeeVisitor v);
}

public class HourlyEmployee : Employee
{
    public override void Accept(IEmployeeVisitor v)
    {
        v.Visit(this);
    }
}

public class SalariedEmployee : Employee
{
    public override void Accept(IEmployeeVisitor v)
    {
        v.Visit(this);
    }
}

public interface IEmployeeVisitor
{
    public void Visit(HourlyEmployee he);
    public void Visit(SalariedEmployee se);
}

public class QtdHoursAndPayReport : IEmployeeVisitor
{
    public void Visit(HourlyEmployee he)
    {
        // generate the line of the report.
    }

    public void Visit(SalariedEmployee se)
    {
    } // do nothing
}

Now, the report can be generated by creating a QtdHoursAndPayReport instance, and then iterating over all the Employee instances and calling accept and passing in the visitor:

IEmployeeVisitor v = new QtdHoursAndPayReport();
for (...) // each employee e
{
    e.Accept(v);
}

The first polymorphic (virtual) dispatch happens when calling e.Accept(v), and it's based on the runtime type of e. The second dispatch is happening when calling v.Visit(this) and it's based on the runtime type of v.

Visitor Pattern as a Functional Programming Paradigm

The design Visitor Pattern imposes is a lot closer to the functional programming paradigm than the OOP paradigm.

To understand why, take a look at the IEmployeeVisitor interface:

public interface IEmployeeVisitor
{
    public void Visit(HourlyEmployee he);
    public void Visit(SalariedEmployee se);
}

Any class that implements IEmployeeVisitor represents an operation. And it needs to define this operation for every possible type of EmployeeHourlyEmploye and SalariedEmployee. Functional programming promotes program decomposition in exactly this manner.

In functional programming, operations refer to functions that manipulate data by transforming or generating new values, whereas variants refer to data structures that represent alternative forms of the same type of value. Every operation has an implementation for every variant. This is related to how Visitor Pattern works: it represents an operation, and it provides an implementation for every type of variant.

Pattern Matching vs Visitor Pattern

As C# is becoming more and more functional and pattern matching gets more advanced, it is now possible to substitute the Visitor Pattern using type patterns.

public static class ReportGenerators
{
    public static string QtdHoursAndPayReport(Employee e) => e switch
    {
        HourlyEmployee he => "Hourly Employee",
        SalariedEmployee se => "",
        _ => throw new ArgumentOutOfRangeException(nameof(e), e, null)
    };
}

public abstract class Employee
{
    public void PrintReport(Func<Employee, string> reportGenerator)
    {
        Console.WriteLine(reportGenerator(this));
    }
}

public class HourlyEmployee : Employee
{
}

public class SalariedEmployee : Employee
{
}

var employees = new List<Employee>() { new HourlyEmployee(), new SalariedEmployee() };
foreach (var e in employees) // each employee e
{
    e.PrintReport(ReportGenerators.QtdHoursAndPayReport);
}

Takeaways

Visitor Pattern is one of the fundamental design patterns, known for its unique capability to extend a hierarchy of classes. But, as C# is progressing towards supporting more functional designs it is also increasing support for pattern matching, including type-testing patterns. Since this is one of the core ideas behind the Visitor Pattern, this means we can implement Visitor-like functionalities using novel C# syntax that includes type-matching patterns and switch expressions.

References: