Visitor pattern

Definition

Represent an operation to be performed on the elements of an object structure. Visitor lets you define a new operation without changing the classes of the elements on which it operates.

Introduction

When going into enterprise application development, you will be working more and more with object structures. Such structures can range from a collection of objects, object inheritance trees, to complex structures comprising of a composite implemented using the Composite structural pattern. As you start working, you will be adding operations to the elements of such structures and distributing the operations across the other elements of the structure.

This complexity can quickly lead to a messy system that’s hard to understand, maintain, and change. Imagine, you or some other programmers later need to change the class of one such element to address some new requirements. Initially, understanding the code itself is a big challenge. Think in terms of understanding a class with over thousand lines of code. I’ve seen this type of class too many times in legacy code. One large class, with just one public method, and over one thousand lines of code.

It’s extremely time consuming to just understand what the class is trying to do. The smallest of changes need to be delicately thought out to ensure you’re not breaking things.

Let’s start with an example of a mail client configurator application. The requirements state that the application should allow users to configure and use the open source Opera and Squirell mail clients in Windows and Mac environments. Sounds simple – So let’s start coding by creating an interface containing the operations of the mail clients and the subclasses, one each for the mail clients.

The design of our object structure seems simple enough and you may be wondering why we need another pattern because our code is getting all the right things done. Yes, we have functional code. But the code is not maintainable. Changing requirements are difficult to implement.

Imagine that a new requirement comes in to provide support for Linux. We not only need to update the MailClient interface with a new configureForLinux() method, we will also need to update each of the concrete subclasses to provide implementation of the new configureForLinux() method. Things might not appear as bad in the current structure as we have only two concrete classes, but consider providing configuration support on Linux for more than 30 mail clients that our application supports.

You can see how evolving requirements will cause our current design to eventually become unmaintainable.

What is the alternative? How do we handle such designs? One answer is for us to follow the Divide and Conquer strategy by applying the Visitor pattern. By using the Visitor pattern, you can separate out an algorithm present in the elements of an object structure to another object, known as a visitor. This is exactly what the GoF means in the first sentence of the Visitor patterns’ intent “Represent an operation to be performed on the elements of an object structure.”

A practical result of this separation is the ability to add new operations to the elements of an object structure without modifying it – One way to follow the Open Closed principle. Again, this is exactly what the GoF means when it says in the second sentence of the intent – “Visitor lets you define a new operation without changing the classes of the elements on which it operates.”

Participants

To understand how the Visitor pattern works, let’s continue with the mail client configurator application. By now, we have realized that the design mistake we made was having the configuration algorithm embedded in the elements of the object structure. So, as a solution we will separate out the configuration algorithms from the elements to visitors. The elements of our object structure will remain the same – we will have the MailClient interface and concrete subclasses for configuring and using different mail clients. Let’s model three subclasses: OperaMailClient, SquirrelMailClient, and ZimbraMailClient. What will now differ is the operations that goes into the interface that the subclasses will implement. We will replace all the configureForXX() methods in the MailClient interface with a single visit() method that will take as input a vistor object. More on this later.

We also need to create the visitors. We need a visitor interface, say MailClientVisitor containing a visit() methods to perform operations on each of the mail clients we have. Concrete visitor classes override the visit() methods of MailClientVisitor to implement the mail client configuration algorithms. Let’s name the visitor classes MacMailClientVisitor, WindowsMailClientVisitor, and LinuxMailClientVisitor.

This is how our class diagram looks like after applying the Visitor pattern.

  1. Element (MailClient): Is an interface that contains accept() method that takes a visitor as an argument.
  2. ConcreteElement (OperaMailClient, SquirrelMailClient, and ZimbraMailClient): Implements the accept() method declared in Element.
  3. Visitor (MailClientVisitor): Is an interface that declares a visit() method for each class of ConcreteElement in the object structure.
  4. ConcreteVisitor (MacMailClientVisitor, WindowsMailClientVisitor, and LinuxMailClientVisitor): Are the concrete classes that implements each method declared by Visitor.

After rewriting the application using Visitor pattern:

All the concrete visitors, WindowsMailClientVisitor, MacMailClientVisitor, and LinuxMailClientVisitor that we wrote above implement the visit() methods. For the purpose of illustration, we have just printed out some messages, but in a real-world application, the algorithms for configuring different mail clients for a particular environment will go in these visit() methods.

Let’s revisit our discussion on double dispatch in the Visitor pattern. Carefully observe that in our current design, different visitors can visit the same concrete element. For example, MacMailClientVisitor, WindowsMailClientVisitor, and LinuxMailClientVisitor are different visitors that can visit the concrete element, OperaMailClient. Similarly, different concrete elements can be visited by the same visitor. For example, OperaMailClient, SquirellMailClient, and ZimbraMail are different concrete elements that can be visited by MacMailClientVisitor. Therefore, a named operation performed using the Visitor pattern depends on the visitor and the concrete element (double dispatch). This is in contrast to what happens when we perform a regular method invocation in Java (single dispatch). In single dispatch, method invocation depends on a single criteria: The class of the object on which the method needs to be invoked.

Summary

If you’ve found the Visitor pattern complex, as compared to the other GoF behavioral patterns, don’t worry because you’re not alone. The Visitor pattern is often conceived as overly complex. It stems from the fact that a visitor can visit a collection of different object, a composite created by applying the Composite pattern, or an inheritance tree. A clear understanding and careful decision is required before using Visitor, else it can make your code unnecessarily complex. But in the right situations, the Visitor Pattern can be an elegant solution to complex situations.

Visitor pattern in Spring framework

When it comes to the Spring Framework, you will observe that Spring implements the Visitor design pattern with org.springframework.beans.factory.config.BeanDefinitionVisitor for beans configuration. A BeanDefinitionVisitor is used to parse bean metadata and resolve them into String or Object that are set into BeanDefinition instances associated with analyzed bean. Obviously, the requirements for Spring’s IoC container are complex. You can examine the related Spring Framework code to see how the Visitor pattern has provided an elegant solution to this complex use case.

Reading material

https://github.com/explorer436/programming-playground/tree/main/java-playground/design-pattern-samples/design-pattern-examples

  1. https://springframework.guru/gang-of-four-design-patterns/visitor-pattern/