The Visitor pattern lets an operation to be executed on different type of objects. The operation is defined in a separate class, so that is decoupled from the objects to be processed.
A Visitor class contains the operations for the different type of objects. The objects implement a common interface to get visible.
Multiple Visitor can be created for the same set of objects.
Visitor is a behavioral pattern.
Let’s see a Java example for the Visitor pattern. The next example is about a smartphone and its installed apps. The used memory size by the apps is calculated with the help of the Visitor pattern.
The installed apps are different type of objects, but they all implement the Visitable
interface.
This interface has one method, which receives the Visitor
object.
package com.programcodex.designpatterns.visitor;
public interface Visitable {
public void accept(Visitor visitor);
}
Before the introduction of the Visitor
interface,
let’s create a few classes those implement the Visitable
interface.
First is the BankApp
that registers its memory usage in megabytes.
The accept
method receives a Visitor
instance as a parameter and
calls its visit
method with this
.
package com.programcodex.designpatterns.visitor;
public class BankApp implements Visitable {
private int usedMemoryInMB = 81;
public int getUsedMemoryInMB() {
return usedMemoryInMB;
}
@Override
public void accept(Visitor visitor) {
visitor.visit(this);
}
}
The EMailApp
registers its memory usage in bytes and
also implements the Visitable
interface.
The interface is implemented in the same way, but there is a difference in it.
Here the type of the this
is EMailApp
,
so this visit
method is another method of the Visitor
class.
package com.programcodex.designpatterns.visitor;
public class EMailApp implements Visitable {
private int usedMemoryInBytes = 970_000;
public int getUsedMemoryInBytes() {
return usedMemoryInBytes;
}
@Override
public void accept(Visitor visitor) {
visitor.visit(this);
}
}
The BrowserApp
stores its memory usage data in a map.
Of course, it also implements the Visitable
interface.
package com.programcodex.designpatterns.visitor;
import java.util.HashMap;
import java.util.Map;
public class BrowserApp implements Visitable {
public static final String USED_MEMORY_IN_MB = "usedMemoryInMB";
private Map<String, Object> appData;
public BrowserApp() {
appData = new HashMap<>();
appData.put(USED_MEMORY_IN_MB, 102);
}
public Map<String, Object> getAppData() {
return appData;
}
@Override
public void accept(Visitor visitor) {
visitor.visit(this);
}
}
It's time to create the Visitor
interface.
It defines visit
methods for all above app classes.
package com.programcodex.designpatterns.visitor;
public interface Visitor {
public void visit(BankApp bankApp);
public void visit(EMailApp eMailApp);
public void visit(BrowserApp browserApp);
}
The MemoryUsageVisitor
implements the Visitor
interface.
The purpose of this class to calculate the total memory usage of the apps, but other visitors also can be created.
Each visit
method receives an object and use it according to its type.
Every method gets called and increments the usedMemoryInBytes
field.
At the end of the process, this field will contain the total memory consumption.
package com.programcodex.designpatterns.visitor;
import java.util.Map;
public class MemoryUsageVisitor implements Visitor {
private long usedMemoryInBytes;
@Override
public void visit(BankApp bankApp) {
System.out.println("visit(BankApp) is running...");
usedMemoryInBytes +=
bankApp.getUsedMemoryInMB() * 1_000_000;
}
@Override
public void visit(EMailApp eMailApp) {
System.out.println("visit(EMailApp) is running...");
usedMemoryInBytes += eMailApp.getUsedMemoryInBytes();
}
@Override
public void visit(BrowserApp browserApp) {
System.out.println("visit(BrowserApp) is running...");
Map<String, Object> data = browserApp.getAppData();
int mb = (int) data.get(BrowserApp.USED_MEMORY_IN_MB);
usedMemoryInBytes += mb * 1_000_000;
}
public void printReport() {
double mb = usedMemoryInBytes / 1_000_000F;
System.out.format("\nThe total used memory: %.2f MB%n", mb);
}
}
The apps are installed on the SmartPhone
and stored in a List
.
The getMemoryUsage
method creates a MemoryUsageVisitor
instance,
iterates through the apps and calls their accept
methods with the visitor
argument.
At the end of the iteration, the visitor
contains the result,
which is printed out by the printReport
method.
package com.programcodex.designpatterns.visitor;
import java.util.ArrayList;
import java.util.List;
public class SmartPhone {
private List<Visitable> installedApps;
public SmartPhone() {
installedApps = new ArrayList<>();
installedApps.add(new BankApp());
installedApps.add(new BrowserApp());
installedApps.add(new EMailApp());
}
public void getMemoryUsage() {
MemoryUsageVisitor visitor =
new MemoryUsageVisitor();
installedApps.forEach(app -> app.accept(visitor));
visitor.printReport();
}
}
And we are done. It’s time to run these things.
The BankApp
uses 81 MB, the EMailApp
0,97 MB
and the BrowserApp
consumes 102 MB. That is 183,97 MB.
package com.programcodex.designpatterns.visitor;
public class TestVisitor {
public static void main(String[] args) {
SmartPhone smartPhone = new SmartPhone();
smartPhone.getMemoryUsage();
}
}
The output of the above test class: