The Visitor design pattern is a behavioral design pattern that allows you to separate algorithms from the objects on which they operate. This pattern is particularly useful when you have a complex object structure and must perform operations across the entire structure without modifying the objects.

Structure

The Visitor pattern consists of the following key components:

  • Visitor: is an interface that declares a visit operation for each type of concrete element in the object structure
  • Concrete Visitor: concrete implementation of the Visitor interface and provides implementations for all methods
  • Element: defines an accept operation that takes a Visitor as an argument
  • Concrete Element: concrete implementation of the Element interface
  • Client: allows Visitor to visit its Elements

When to use the Visitor Pattern?

  • When you want to perform operations on objects of a complex structure
  • When you want to add new operations to existing object structures without modifying those structures (Open/Closed Principle)

Implementation Example

The code below demonstrates the Visitor design pattern for a simple file system in Java. The FileSystemElement interface represents the elements in the file system, with File and Directory as concrete implementations. Each File has a name and format, while each Directory contains a list of FileSystemElement objects, allowing for a nested structure. The Visitor interface defines visit methods for both File and Directory. Two concrete visitors, TotalNumberOfFilesVisitor and TotalSizeVisitor, implement the Visitor interface to calculate the total number of files and the total size of the directory structure, respectively. In the main method of the Solution class, a file system hierarchy is created, and these visitors are used to compute and print the total number of files and their total size based on the format, demonstrating the separation of algorithms from the object structure.

import java.util.*;

// Client class
public class Solution {

    public static void main(String[] args) {

        Directory root = new Directory("root");
        Directory music = new Directory("music");
        Directory documents = new Directory("documents");

        root.addElement(music);
        root.addElement(documents);

        music.addElement(new File("song1.mp3"));
        music.addElement(new File("song2.mp3"));

        documents.addElement(new File("document1.doc"));
        documents.addElement(new File("document2.doc"));

        TotalNumberOfFilesVisitor totalNumberOfFilesVisitor = new TotalNumberOfFilesVisitor();
        root.accept(totalNumberOfFilesVisitor);

        System.out.println("Total number of files: " + totalNumberOfFilesVisitor.getNumberOfFiles());

        TotalSizeVisitor totalSizeVisitor = new TotalSizeVisitor();

        music.accept(totalSizeVisitor);
        System.out.println("Total size of music files: " + totalSizeVisitor.getTotalSize());

        totalSizeVisitor = new TotalSizeVisitor();
        documents.accept(totalSizeVisitor);
        System.out.println("Total size of document files: " + totalSizeVisitor.getTotalSize());

        totalSizeVisitor = new TotalSizeVisitor();
        root.accept(totalSizeVisitor);
        System.out.println("Total size of the root directory: " + totalSizeVisitor.getTotalSize());
    }
}

// Element interface
interface FileSystemElement {

    void accept(Visitor visitor);
}

// Concrete Elements
class File implements FileSystemElement {

    private String name;
    private String format;

    public File(String name) {

        this.name = name;

        if (name.contains(".mp3")) {
            this.format = ".mp3";
        } else {
            this.format = ".doc";
        }
    }

    public String getName() {

        return name;
    }

    public String getFormat() {

        return format;
    }

    public void accept(Visitor visitor) {

        visitor.visit(this);
    }
}

class Directory implements FileSystemElement {

    private String name;

    private List<FileSystemElement> elements = new ArrayList<>();

    public Directory(String name) {

        this.name = name;
    }

    public String getName() {

        return name;
    }

    public void addElement(FileSystemElement element) {

        elements.add(element);
    }

    public void accept(Visitor visitor) {

        visitor.visit(this);

        for (FileSystemElement element : elements) {

            element.accept(visitor);
        }
    }
}

// Visitor interface
interface Visitor {

    void visit(File file);
    void visit(Directory directory);
}

// Concrete Visitors
class TotalNumberOfFilesVisitor implements Visitor {

    private int numberOfFiles = 0;

    public void visit(File file) {

        numberOfFiles += 1;
    }

    public void visit(Directory directory) {

        
    }

    public int getNumberOfFiles() {

        return numberOfFiles;
    }
}

class TotalSizeVisitor implements Visitor {

    private int totalSize = 0;

    public void visit(File file) {

        if (file.getFormat().equals(".mp3")) {
            totalSize +=3;
        } else {
            totalSize += 1;
        }
    }

    public void visit(Directory directory) {

    }

    public int getTotalSize() {

        return totalSize;
    }
}

The output of the following program will be:

Total number of files: 4
Total size of music files: 6
Total size of document files: 2
Total size of the root directory: 8