Visitor Design Pattern

Processing Logic : The visitor(processor) comes with a purpose. It knows how to visit(process) each element but, does not know how to traverse the whole object.
Traversal Logic :
It invites(accept) the visitor and guides him to visit all the elements.

At the end of its visit, the visitor becomes ready with its report on the whole object.

Its a behavioral design pattern that help us keep our processing logic independent of the structure of the object. In other words, it allows us add features for an object externally without having to understand its internal structure. Hence, its very useful in developing features for the complex and highly configurable objects.

 

How does the pattern work?

Lets say a computer manufacturer wants to build a support system for its products. As part of this it wants to build several features such as:

  • HealthScanProcessor : This will go through the key components and run its test cases to verify their health.
  • SupportGuideGenerator : It wants to standardize and automate these guides as we use many common components across our models.
  • ProductSpecificationReport: This report will provide the details on the components used in the model.
What’s the problem ?

When we look at these features, each one needs to traverse through a given model and process each of its individual element.

Firstly, these system can have a variety of configurations. Hence, the traversal logic is not going to be easy. Secondly, the configurations will go through lots of changes with the new models. Hence, the maintenance of the traversal logic across all our features will be quite an effort.

Thus, all our support features are going to go through lots of maintenance work!

How to solve this using a Visitor Pattern?

As shown in the diagram, both the features traverse through the ProductModel and process the individual elements in it.

But, even though the traversal logic is the same, we have to maintain it in both the places. Moreover, both need to know the structure of the product and the APIs for it.

Design with and without a Visitor Pattern

In order to simplify the problem, the Visitor Pattern says, why not have a common traversal logic for all our features !

When we separate out the complex traversal logic, adding a new feature boils down to handling the individual elements. The Visitor pattern allows us to insert the specific processing of individual elements into a common traversal logic as shown above.

As we can notice, when we accept the HealthScanVistor, it will visit a WebCam for checking its health. Whereas if we accept the SupportGuideVisitor, it will visit the WebCam for retrieving its support guide content from the document repository.

Now all the visitors use a common traversal logic. And, at the end of the traversal, the visitor visits all the elements and, thereby, processes the whole object.

Here is the UML diagram of the Visitor pattern we discussed above.

 

Let us implement and check a demo

Having seen the design lets now look at its implementation.

 

Part 1: The Object & the Traversal Logic

The LaptopModelX is the key object here which holds the traversal logic. It can accept any visitor and make him visit its elements.

package spectutz.dp.behavior.visitor.pc;
import spectutz.dp.behavior.visitor.pc.visitors.IVisitor;

public class LaptopModelX implements ILaptopModel {
	private WebCam webCam;	
	private Battery battery;	
	private MemoryCards memoryCards;	
	
	public LaptopModelX(WebCam webCam, Battery battery, MemoryCards memoryCards) {
		this.webCam = webCam;
		this.battery = battery;
		this.memoryCards = memoryCards;
	}
	public WebCam getWebCam() {
		return webCam;
	} 
	public MemoryCards getMemoryCards() {
		return memoryCards;
	} 
	public Battery getBattery() {
		return battery;
	} 
	
	//Accepts the visitor and makes him visit all it's elements
	public void accept(IVisitor visitor) {
		visitor.visit(webCam);
		visitor.visit(battery);
		visitor.visit(memoryCards);
	}

}
//Note: Depending on our use case we can have a generic accept(IVisitor) method 
//1. At an abstract base class 
//2. A separate class for managing the traversal logic
package spectutz.dp.behavior.visitor.pc;

public interface ILaptopModel {
	
	public WebCam getWebCam(); 
	public MemoryCards getMemoryCards(); 
	public Battery getBattery(); 

}
package spectutz.dp.behavior.visitor.pc;

public class Battery{
}
package spectutz.dp.behavior.visitor.pc;

public class MemoryCards{
}
package spectutz.dp.behavior.visitor.pc;

public class WebCam{
}

 

Part 2: The Visitor

The HealthScanVisitor implements a feature to check the health status of the components of a computer. As we can see it does not have any information on the structure of the product it would be scanning.

package spectutz.dp.behavior.visitor.pc.visitors;

import java.util.ArrayList;
import java.util.List;

import spectutz.dp.behavior.visitor.pc.Battery;
import spectutz.dp.behavior.visitor.pc.MemoryCards;
import spectutz.dp.behavior.visitor.pc.WebCam;

public class HealthScanVisitor implements IVisitor{
	List<String> findings = new ArrayList<String>(); 
	
	public void visit(WebCam webCam) {
		System.out.println("Scanning webCam for health check...");
		findings.add("WebCam is OK.");
	}
	public void visit(Battery battery) {
		System.out.println("Scanning battery for health check...");
		findings.add("Battery is working with 40% performance.");
	}
	public void visit(MemoryCards memoryCards) {
		System.out.println("Scanning memory cards for health check...");
		findings.add("Memory cards : \n Slot 1: 8GB Status: Ok \n Slot 2: empty");
	}
	
	public void buildOutput() {
		System.out.println("Component Health Status :\n"); 
		
		findings.forEach(System.out::println);
	}
}

//Notes: 
//As there are lots of components, we should be using a factory to implement the visit method
//We have avoided a factory, to keep the example simple and focus on the visitor
//The sole purpose here is to provide a visit method for each individual component
package spectutz.dp.behavior.visitor.pc.visitors;

import spectutz.dp.behavior.visitor.pc.Battery;
import spectutz.dp.behavior.visitor.pc.MemoryCards;
import spectutz.dp.behavior.visitor.pc.WebCam;

public interface IVisitor {
	public void visit(WebCam webCam);
	public void visit(Battery battery);
	public void visit(MemoryCards memoryCards);
}

The visitor contains the processing logic for each individual elements. In a similar fashion we can add any new feature.

 

The Demo Time

The demo uses a product model LaptopModelX which accepts a visitor for generating a health check report.

Along with the demo code we have its output at its end.

It just shows, how the visitor processes the object when it completes its visit, without having to know the structure of the object.

package spectutz.dp.behavior.visitor;

import spectutz.dp.behavior.visitor.pc.Battery;
import spectutz.dp.behavior.visitor.pc.LaptopModelX;
import spectutz.dp.behavior.visitor.pc.MemoryCards;
import spectutz.dp.behavior.visitor.pc.WebCam;
import spectutz.dp.behavior.visitor.pc.visitors.HealthScanVisitor;

public class PCVisitorDemo{
	public static void main(String[] args) {		
		LaptopModelX modelX = new LaptopModelX(new WebCam(), 
											   new Battery(), 
											   new MemoryCards());
				
		//Invite the visitor and make him visit your components
		HealthScanVisitor scanHealthVistor = new HealthScanVisitor();
		modelX.accept(scanHealthVistor);
		
		System.out.println("\n***Visit completed. Lets print the report.***\n");
		scanHealthVistor.buildOutput();
	}
}

/*
Scanning webCam for health check...
Scanning battery for health check...
Scanning memory cards for health check...

***Visit completed. Lets print the report.***

Component Health Status :

WebCam is OK.
Battery is working with 40% performance.
Memory cards : 
 Slot 1: 8GB Status: Ok 
 Slot 2: empty
*/

 

Summary of the benefits

If we have to add features to objects with complex and widely varying structures, the Visitor pattern proves very useful.

A common use case being when we are building features against the complex code structures. The syntax highlighters, code review tools, compilers for instance, heavily use visitors to simplify their design.

In short, it keeps the processing logic clean and simple by separating it from the traversal logic of complex structures.