Java programming language logo

Visitor Design Pattern

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.

Example

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:

The Visitor Design Pattern