This article is now available in YouTube
According to Wikipedia, the visitor design pattern is a way of separating an algorithm from an object structure on which it operates. If we have a hierarchy of classes, then we can make some operations on those classes without modifying those classes using Visitor design pattern. So let us see how this is possible.
I am using a C# console application using VS2022 IDE for this.
Since this pattern is about hierarchy of objects, I have created 3 classes: Person, Manager and Employee. Person is the base class which is inherited by both Manager and Employee
Person class has FirstName and LastName as properties.
Employee class extends Person class and has additional ReportingTo property to indicate the Manger to which he/she reports.
Manager class also extends Person and has additional NumReports property to indicate how many Employees report to him/her.
public class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
public Person(string firstName, string lastName)
{
this.FirstName = firstName;
this.LastName = lastName;
}
}
public class Manager : Person
{
public int NumReports { get; set; }
public Manager(string firstName, string lastName, int numReports) : base(firstName, lastName)
{
NumReports = numReports;
}
}
public class Employee : Person
{
public string ReportingTo { get; set; }
public Employee(string firstName, string lastName, string reportingTo) : base(firstName, lastName)
{
ReportingTo = reportingTo;
}
}
Now assume we have collection of these objects and we need to print some description based on the type of the object. Eg. for Employee we need to print to whom he/she reports to and for Manager we need to print number of reports. To achieve this we need to loop through the collection and based on the runtime instance type we need to print differently like this.
public static class StartUp
{
public static void Main()
{
var people = new List<Person>() { new Person("John", "Doe"),
new Manager("Bob", "Manager", 5),
new Employee("Bob", "Employee", "Bob Manager") };
foreach (var person in people)
{
if (person is Manager)
{
var manager = (Manager)person;
Debug.WriteLine($"{manager.LastName} has {manager.NumReports} reports.");
}
else if (person is Employee)
{
var employee = (Employee)person;
Debug.WriteLine($"{employee.LastName} reports to {employee.ReportingTo}");
}
else
{
Debug.WriteLine(person.LastName);
}
}
Console.ReadLine();
}
}
Now you can see different messages are printed based on the object type in the Output window.
Doe
Manager has 5 reports.
Employee reports to Bob Manager
This code works but as more number of classes inherit from Person, more if conditions needs to be added. This is a code smell that can be fixed by extracting this code in another class and having a method for each Person hierarchy using method overloading. Something like this.
public class Printer
{
public virtual void Print(Person person)
{
Debug.WriteLine(person.LastName);
}
public virtual void Print(Employee employee)
{
Debug.WriteLine($"{employee.LastName} reports to {employee.ReportingTo}");
}
public virtual void Print(Manager manager)
{
Debug.WriteLine($"{manager.LastName} has {manager.NumReports} reports.");
}
}
Now we can change the code to call the new Printer class.
public static void Main()
{
var people = new List<Person>() { new Person("John", "Doe"),
new Manager("Bob", "Manager", 5),
new Employee("Bob", "Employee", "Bob Manager") };
var printer = new Printer();
foreach (var person in people)
{
printer.Print(person);
}
Console.ReadLine();
}
Let us run this and see if this works.
Doe
Manager
Employee
But as you see from the result above, this always prints only the name, which means only the first overloaded method is called. This is because the method to be called on the printer object is resolved by the compile time instance of the person object argument passed. Since we have declared the collection as List<Person>, the compiler always chooses the method that takes Person as its parameter. This is known as compile time or static binding. and method overloading are resolved during compile time. How can we fix this?
The solution is to resolve the methods based on runtime instance type. We know that method overriding is resolved at runtime. If we can have some method on the Person object, then we can easily fix this issue.
First let us create a method on Person class that can be overridden by its child classes and see how that works.
public class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
public Person(string firstName, string lastName)
{
this.FirstName = firstName;
this.LastName = lastName;
}
public virtual void PrintMe(Printer print)
{
print.Print(this);
}
}
We can now override this method in its derived classes.
public class Manager : Person
{
public int NumReports { get; set; }
public Manager(string firstName, string lastName, int numReports) : base(firstName, lastName)
{
NumReports = numReports;
}
public override void PrintMe(Printer print)
{
print.Print(this);
}
}
public class Employee : Person
{
public string ReportingTo { get; set; }
public Employee(string firstName, string lastName, string reportingTo) : base(firstName, lastName)
{
ReportingTo = reportingTo;
}
public override void PrintMe(Printer print)
{
print.Print(this);
}
}
Now let us change the code in the Main method to invoke the newly created function on the Person class.
foreach (var person in people)
{
/* printer.Print(person); */
person.PrintMe(printer);
}
We will run the application and see the Output window.
Doe
Manager has 5 reports.
Employee reports to Bob Manage
As you can this works as expected. What we have effectively done is change the methods to be resolved based both on the object’s runtime type and the first argument passed. This is called as double dispatch. Languages like C# and Java only support single dispatch (i.e. resolve method based on the invoked object ‘s runtime type). Using the visitor pattern we have mimicked double dispatch. This is the crux of Visitor design pattern.
Now if we want to perform another operation on these classes (.e. Person hierarchy), say printing to Console rather than Output window, we can extend the Printer class and do it.
public class ConsolePrinter: Printer
{
public override void Print(Person person)
{
Console.WriteLine(person.LastName);
}
public override void Print(Employee employee)
{
Console.WriteLine($"{employee.LastName} reports to {employee.ReportingTo}");
}
public override void Print(Manager manager)
{
Console.WriteLine($"{manager.LastName} has {manager.NumReports} reports.");
}
}
And now change the below line in the Main method to instantiate the ConsolePrinter
/* var printer = new Printer(); */
var printer = new ConsolePrinter();
Running the application will be print in the console rather than the output window. We have hierarchy of objects (Person, Manager and Employee) and now we have added a new operation on these classes without modifying those classes. This is the use of Visitor design pattern.
By convention in Visitor Pattern, the PrintMe method will be called as Accept and the Printer class will be called as Visitor and Print method on Printer class will be called as Visit.
Hope you found this post useful. Please share this with whomever you think might find useful.
Happy coding!