Basics of Design Patterns — Visitor Pattern🚶

Martin Jurran
Software Design Patterns
7 min readJan 22, 2024

The visitor pattern allows you to seperate algorithmns from the objects on which they operate.

Real-World Analogy 🌍

A traveller always needs to be prepared for the sights visited.

Imagine that you are on vacation and visiting various sights. You have compiled a list of places to visit. Depending on the type of place, you need to bring some necessities.

Let’s imagine we go to Korea.

  • If it’s the Korean Demilitarized Zone, possibly some identification, such as a passport.
  • If it’s Busan, probably some bathing suits and towels to enjoy the beaches.
  • If it’s Jeju, possibly a set of good hiking boots to enjoy hiking at Hallasan.

Problem 🤔

The Korea Itinerary that is to be exported as JSON

Imagine, you are a member of a team that works on a vacation planning app. The app allows users to plan and book their vacation by selecting different cities, sightseeing locations, and attractions they would like to visit.

The app works by storing all vacation information in a single graph structure. Every node either represents a city or attraction. Each node is connected to other nodes if there’s a road or path between them in the real world.

Your team has been tasked with adding the capability for users to export their vacation itinerary in JSON format. Initially, you planned to add an JSON export method to each of the node classes.

/**
* BAD EXAMPLE
*/

class Sight {
//...

public string GetJson() {
//...
}
}

class Beach {
//...

public string GetJson(){
//...
}
}
class Mountain {
//...

public string GetJson(){
//...
}
}

This meant that you’d have to modify each class to include the export method.

However, members of the project team raised concerns about this approach. They pointed out that the existing node classes had been developed and tested extensively, and they didn’t want to risk introducing bugs in the production system. Making modifications to these classes could pose a risk to the entire application.

They also raised concerns about the appropriateness of having the JSON export code within the node classes (POCOS). These classes were primarily designed to handle vacation point of interest data, and it could be confusing to have unrelated logic within them. It might also lead to future changes to the classes, which could be risky and further complicate the code.

Additional reasons for concern may be that, with alike requirements, usually other export data types are requested later. That would require to change these basic classes again.

Solution đź’ˇ

The Visitor Pattern allows you to place the new behavior in a separate class called Visitor instead of implementing logic directly into existing classes.

/**
* GOOD EXAMPLE: ExportJsonVisitor and Client Implementation
*/

public class ExportJsonVisitor {
public string GetJson(Sight sight) {
//...
}

public string GetJson(Beach beach) {
//...
}

public string GetJson(Mountain mountain) {
//...
}
}

public class ExportXMLVisitor {
// ...
}

public class Client {
IEnumeration<IEntity> Entities; //list of all travel steps

ExportJsonVisitor ExportJsonVisitor = new ExportJsonVisitor();
ExportXMLVisitor ExportXMLVisitor = new ExportXMLVisitor();

public void Run() {
foreach(var entity in Entities) {
entity.GetExport(ExportJsonVisitor); //json impementation

entity.GetExport(ExportXMLVisitor); //xml impementation
}
}
}

To implement the Visitor Pattern, you would create a separate class ExportJsonVisitor, which would contain the desired behavior. You would then pass the object that needs to perform the behavior as an argument (passing this) to one of the visitor’s methods, giving the method access to all the data within the object.

public class Mountain : IVisitor {
public string GetExport(ExportXMLVisitor exportXMLVisitor) {
/**
* double dispatch => pointing back to visitor implementation
*/
exportXMLVisitor.GetXml(this);
}

public string GetExport(ExportJsonVisitor exportJsonVisitor) {
/**
* double dispatch => pointing back to visitor implementation
*/
exportJsonVisitor.GetJson(this);
}
}

However, in the case of different behavior across different classes, the visitor class would need to define a set of methods with different argument types. To ensure that the appropriate method is executed for each object, the Visitor pattern uses a technique called Double Dispatch.

Double Dispatch essentially delegates the choice of which method to execute to the object itself. When an object is passed to a visitor, the object “accepts” the visitor and selects the appropriate visiting method to be executed, based on its own class.

While implementing this pattern may require some minor changes to the node classes, it provides a more flexible and scalable solution in the long run. By creating a common interface for all visitors, any new behavior introduced can be executed across all existing nodes, and any new additions can be made simply by implementing a new visitor class.

Structure 🏗️

Structure of an example implementation, mermaid diagram code attached.
  1. Visitor interface that declares a set of visiting methods. These methods take concrete elements of an object structure as arguments. In programming languages that support method overloading, these methods can have the same name but must have different parameter types.
  2. Concrete Visitors are then created to implement several versions of each behavior, tailored for different concrete element classes. These visitors can be added to the program to introduce new behaviors to existing elements without modifying their classes.
  3. Element interface that declares a method for accepting visitors. This method takes in a parameter declared with the type of the Visitor interface. Each Concrete Element must implement the accept method, redirecting the call to the proper visitor’s method corresponding to the current element class. It’s important to note that even if a base element class implements this method, all subclasses must still override it and call the appropriate method on the visitor object.
  4. Concrete element classes must implement the acceptance method. Clients work with objects from this collection through an abstract interface, allowing them to interface with any visitor that’s introduced into the system.
  5. Client usually represents a collection or other complex objects, manages the Business Logic.

How to Implement

To implement the Visitor Design Pattern, you need to create two interfaces:

  • one for visitors (IVisitor) and
  • one for elements (IEntity)

The visitor interface has a set of methods, one for each element class in the program. The element interface adds an abstract method (GetJson(E: Sight)) to the base class of the element hierarchy. This method should accept a visitor object as an argument.

To use the Visitor pattern, you implement the acceptance methods in all element classes (GetJson(v IVisitor)). These methods redirect calls to a visiting method on the incoming visitor object, which matches the class of the current element.

The element classes should only interact with visitors through the visitor interface, while visitors must be aware of all element classes referenced as parameter types of the visiting methods.

You can create new visitor classes for any behavior that can’t be implemented inside the element hierarchy. If the visitor needs access to private members of the element class, you can either make those fields or methods public or nest the visitor class in the element class. This is only possible in some programming languages.

Finally, the client creates visitor objects and passes them into elements via GetJson methods to use the Visitor design pattern.

Pros and Cons

Pros

âś… Following Open/Closed Principle. New behavior can be introduced without changing how the underlying objects (classes) work.

âś… Following Single Responsibility Principle. The logic can be versioned and held within the same Visitor Implementation.

âś… Gathering of Information. The visitor implementation can gather useful information while working on the concrete object implementations. This is handy, when trying to traverse complex object structures, such as an object tree. The visitor can be applied to all objects of the tree.

Cons

❌ Maintainability. All visitors need to be updated each time a class gets added or removed to a element hierarchy.

❌ Access. Visitors might not be able to access private fields and methods of objects they are intended to work on.

Relation with other patterns

  • The Vistor pattern can be seen as another version of the Command Pattern. It’s objects can execute operations of various different classes.
  • The Visitor pattern can be used to Execute Operations over a complete complex structure.
  • Combine Visitor and Iterator to traverse acomplex data structure and execute operations over it’s members, even if all of them are different classes.

Attachments

Mermaid Diagram

classDiagram
IVisitor<--ConcreteVisitors
IEntity-->IVisitor
Mountain-->IEntity
Beach-->IEntity
Sight-->IEntity
Client-->ConcreteVisitors
Client-->IEntity
IVisitor-->Mountain
IVisitor-->Beach
IVisitor-->Sight
class IVisitor{
<<interface>>
+GetJson (E: Sight)
+GetJson (E: Beach)
+GetJson (E: Mountain)
}

class ConcreteVisitors{
-...
+GetJson (E: Sight)
+GetJson (E: Beach)
+GetJson (E: Mountain)
}

class IEntity {
<<interface>>
+GetJson(v: IVisitor)
}

class Sight{
+...
+GetDanger()
+GetJson(v: IVisitor)
}

class Beach{
+...
+GetWaterTemp()
+GetJson(v: IVisitor)
}

class Mountain{
+...
+GetWindStrength()
+GetJson(v: IVisitor)
}

class Client {

}

note for Client "object.GetJson(new ConcreteVisitor())"
note for Sight "v.GetJson(this)"

About this series

I noticed personally, that design patterns still remain a mistery to a lot of software engineers, even though these concepts are nothing new and have been around for decades.

I want to solve this problem with this article series to help other software engineers grow.

Feel free to comment in case you find mistakes or want to add your own perspective!

(Photo by the author, Illustrations by Takashi Mifune under free use)

--

--

Martin Jurran
Software Design Patterns

Personal blog of a Software Engineer | Azure DevOps, C#/.NET, JavaScript