Overview
The Visitor Pattern is a behavioral design pattern that allows you to add new operations to a group of objects (elements) without modifying their classes. This is achieved by separating the operation from the objects it operates on, encapsulating it in a separate visitor object.
The pattern promotes open/closed principle: objects remain open for extension through visitors while being closed for modification.
Key Participants
Visitor
Defines an interface for visiting each type of element in the structure.
Declares methods for each concrete element type (e.g., visitConcreteElementA, visitConcreteElementB).
ConcreteVisitor
Implements the operations for each concrete element.
Encapsulates specific behavior that is applied to the elements.
Element
Defines an interface for accepting a visitor.
Typically has an accept(Visitor visitor) method.
ConcreteElement
Implements the Element interface and defines the accept method.
Calls the appropriate method on the visitor, passing itself as an argument.
ObjectStructure
Holds a collection of elements and provides a way to iterate through them for visiting.
Implementation in Code
Example: File System with Visitors for Size Calculation and File Type Display
// Visitor Interface
interface Visitor {
void visitFile(File file);
void visitFolder(Folder folder);
}
// Concrete Visitor: Calculate Total Size
class SizeCalculator implements Visitor {
private int totalSize = 0;
@Override
public void visitFile(File file) {
totalSize += file.getSize();
}
@Override
public void visitFolder(Folder folder) {
// No size directly for folders in this example
}
public int getTotalSize() {
return totalSize;
}
}
// Concrete Visitor: Display File Type
class FileTypeDisplayer implements Visitor {
@Override
public void visitFile(File file) {
System.out.println("File: " + file.getName() + " [" + file.getType() + "]");
}
@Override
public void visitFolder(Folder folder) {
System.out.println("Folder: " + folder.getName());
}
}
// Element Interface
interface Element {
void accept(Visitor visitor);
}
// Concrete Element: File
class File implements Element {
private String name;
private String type;
private int size;
public File(String name, String type, int size) {
this.name = name;
this.type = type;
this.size = size;
}
public String getName() {
return name;
}
public String getType() {
return type;
}
public int getSize() {
return size;
}
@Override
public void accept(Visitor visitor) {
visitor.visitFile(this);
}
}
// Concrete Element: Folder
class Folder implements Element {
private String name;
private List<Element> elements = new ArrayList<>();
public Folder(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void addElement(Element element) {
elements.add(element);
}
@Override
public void accept(Visitor visitor) {
visitor.visitFolder(this);
for (Element element : elements) {
element.accept(visitor);
}
}
}
// Client
public class VisitorPatternDemo {
public static void main(String[] args) {
// Create elements
File file1 = new File("Document", "txt", 120);
File file2 = new File("Presentation", "ppt", 300);
Folder folder = new Folder("Work");
folder.addElement(file1);
folder.addElement(file2);
// Visitor for size calculation
SizeCalculator sizeCalculator = new SizeCalculator();
folder.accept(sizeCalculator);
System.out.println("Total Size: " + sizeCalculator.getTotalSize() + " KB");
// Visitor for file type display
FileTypeDisplayer fileTypeDisplayer = new FileTypeDisplayer();
folder.accept(fileTypeDisplayer);
}
}
Key Methods
accept(Visitor visitor)
Allows the visitor to perform an operation on the element.
Calls the visitor’s corresponding visit method and passes itself as an argument.
visit(Element)
Encapsulates the operation performed on an element.
Advantages
Open/Closed Principle: New operations can be added without modifying the existing element classes.
Separation of Concerns: Different behaviors are encapsulated in separate visitors, simplifying element classes.
Flexibility: A visitor can operate on a group of related objects without changing their structure.
Disadvantages
Tight Coupling: Adding new element types requires modifying all visitors.
Complexity: The pattern introduces additional classes for visitors and can make the code harder to follow.
Not Always Applicable: It works best when the element hierarchy is stable.
Real-World Examples
Compilers: Visitors can traverse abstract syntax trees (AST) to perform operations like type checking or code generation.
Document Processing: A visitor can apply different operations, such as exporting, printing, or spell-checking, to parts of a document.
UI Frameworks: A visitor can traverse and operate on UI components like buttons, text boxes, or labels.
When to Use the Visitor Pattern?
When you want to perform multiple operations on objects without modifying their classes.
When the object structure is stable, but new operations are frequently added.
When you want to separate algorithms from the objects on which they operate.