The Visitor design pattern has been around for a long time, but many people still don’t understand it. However, if you know how to use it, it can be a great way to optimize your code to be more modular and easier to use.
The Visitor design pattern is a behavioral pattern where a visitor object is used to perform operations on other objects. Use the visitor design pattern when adding new operations to an already existing object.
This post shares a real-world example of the Visitor design pattern in C# to better understand its use cases and importance.
What is the Visitor design pattern?
The Visitor design pattern provides an easy way for software developers to change an element object’s executing algorithm using a visitor object. Changing the visitor object also changes the element object’s execution algorithm.
Typically, the best use case of the Visitor design pattern in C# is when you need to perform unique and varying operations across element objects. To put the explanation above in perspective, take a look at the following diagram.
You have a visitor object on the left side and the object structure on the right. The object structure in this case is a collection of list elements. The elements can be a part of the inheritance hierarchy or simply implement a common interface. It’s common for the object structure to have more than one element.
The object structure on the right has three elements. If you need to perform a different operation based on the different class types, a good option is to use the Visitor design pattern.
The visitor object would visit the elements, performing the required operation on each.
Things start getting interesting if you want to perform more than one operation. In this case, you can create multiple Visitors, each serving a different function. You will see an example of this later below.
The Visitor design pattern in C# might not be popular due to its complexity, but it’s a highly functional tool to change elements without altering code.
Generally, the Visitor design pattern is categorized under the behavioral design patterns. It is a way of adding new behavior to the class without changing its code. In other words, this pattern separates an algorithm from the object it operates on.
What is the relationship between double dispatch and the Visitor pattern?
In the Visitor pattern, double dispatch is used to dispatch a call to a method on a visitor object, based on the type of the object being visited. The visitor object contains a method for each type of object that can be visited. When an object is visited, the visitor object dispatches the call to the appropriate method, based on the type of the object being visited.
When to use the Visitor design pattern?
While the Visitor design pattern might be a complex tool, it’s highly effective. However, it’s only helpful if you know when to use it.
The following are instances where you may want to use the Visitor design pattern:
- You need to perform multiple unique operations on an object structure.
- When an object structure needs to remain unchanged, but you need to implement new features.
- When it’s acceptable to expose an object structure’s operations or internal state.
While the above list of instances where the Visitor design pattern shines might not be exhaustive, it’s an excellent starting point.
How to implement the Visitor design pattern in C#? Step-by-step guide
This section will implement a Visitor design pattern example in C#. Here is the step-by-step process of how you can implement the Visitor design pattern.
Step #1: Create the Element interface
Create an interface and name it IElement. The purpose of the IElement is to declare the Accept
method whose argument is the IVisitor
interface.
public interface IElement
{
void Accept(IVisitor visitor);
}
Step #2: Create the Visitor interface
Name the following interface IVisitor. It will declare the Visit
method to take elements from the object structure.
public interface IVisitor
{
void Visit(IElement element);
}
Step #3: Create concrete elements
In this next step, create the concrete class. Name the class file Student.cs. Below is the code to create the concrete element:
public class Student : IElement
{
public string StudentName { get; set; }
public Student(string name)
{
StudentName = name;
}
public void Accept(IVisitor visitor)
{
visitor.Visit(this);
}
}
Step #4: Create the concrete visitors
In this section, create two different concrete visitors. This will help explain the roles of different visitors and how you can use them to perform different operations on the elements in an object structure.
Concrete Visitor #1:
Create another class and name it Physician.cs. It will implement the operations declared by the IVisitor
interface.
public class Physician : IVisitor
{
public string Name { get; set; }
public Physician(string name)
{
Name = name;
}
public void Visit(IElement element)
{
Student student = (Student)element;
Console.WriteLine($"Physician: {Name} conducted a " +
$"health checkup of the student: {student.StudentName}");
}
}
Concrete Visitor #2
For the second visitor, create a class named Cook.cs with the same implementation criteria as the first visitor. Below is the code to create the second visitor class.
class Cook : IVisitor
{
public string Name { get; set; }
public Cook(string name)
{
Name = name;
}
public void Visit(IElement element)
{
Student student = (Student)element;
Console.WriteLine($"Cook: {Name} gave the healthy " +
$"meal to the student: {student.StudentName}");
}
}
Step #5: Create the object structure
Next, create an additional class file and name it College.cs. The College
object structure can iterate through its elements and provide a structure to allow Visitors to visit its elements.
public class College
{
private readonly List<IElement> elements;
public College()
{
elements = new List<IElement>
{
new Student("Todd"),
new Student("Maggie"),
new Student("Stella")
};
}
public void PerformOperation(IVisitor visitor)
{
foreach (var student in elements)
{
student.Accept(visitor);
}
}
}
Step #6: Run the client code
Finally, initialize all classes to see the Visitor design pattern in action.
College college = new College();
var physicianVisitor = new Physician("Mike");
college.PerformOperation(physicianVisitor);
var cookVisitor = new Cook("William");
college.PerformOperation(cookVisitor);
Running the code above should output the following:
Physician: Mike conducted a health checkup of the student: Todd
Physician: Mike conducted a health checkup of the student: Maggie
Physician: Mike conducted a health checkup of the student: Stella
Cook: William gave the healthy meal to the student: Todd
Cook: William gave the healthy meal to the student: Maggie
Cook: William gave the healthy meal to the student: Stella
The console window confirms the output.
In the Visitor design pattern, the Physician and the Cook are the Visitor classes, while the Students are the elements of the Object Structure.
What are the advantages and disadvantages of the Visitor design pattern?
The advantages of the Visitor design pattern are:
- No need to make changes to the existing code.
- It is easy to add a new operation to existing code by creating a new visitor.
- Extending an object’s functionality with the Visitor pattern is more straightforward than other approaches, such as inheritance.
The disadvantages of the Visitor design pattern are:
- It can be difficult to understand the code if you’re unfamiliar with the visitor pattern.
- If not used carefully, it can create coupling between objects.
What are related patterns to the Visitor design pattern?
The Composite pattern and the Interpreter pattern are related to the Visitor design pattern.
The Composite pattern allows an object to be treated as a single entity, even though it may be made up of multiple sub-objects. This is useful for situations where you want to perform an operation on a group of objects without handling each object individually.
The Interpreter pattern is used for situations where you need to evaluate a complex expression, such as a mathematical formula. Breaking the expression down into smaller parts makes it easier to interpret and calculate the final result.
In conjunction with the Visitor design pattern, you can use both of these patterns to provide more flexibility when performing operations on complex data structures.
Difference between the Visitor vs Decorator design pattern?
Both Visitor and Decorator design patterns can dynamically add a behavior to the class. The difference between the decorator and the visitor design pattern is that the Decorator pattern wraps the existing class and enhances the functionality without changing the class. On the other hand, you need to change the existing class and add the Accept method for a Visitor to be able to visit it.
Conclusion
The Visitor design pattern is a powerful tool to have in your arsenal when dealing with object-oriented programming. You can use it in many different ways. Also, it is perfect for adding new operations to an existing system without modifying the original codebase.
In our example, you saw how you can use the Visitor pattern to add behavior to the Student class without changing it. You also looked at some of the advantages and disadvantages of using this pattern.
If you want to learn more about design patterns, check the separate in-depth article about design patterns in C#.