3.6 - Visitor Pattern : Behavioral Design Patterns
The Visitor Pattern is a behavioral design pattern that allows adding new operations to a set of objects (elements) without modifying their structure. The pattern involves creating a visitor class that implements operations for different types of objects. This pattern promotes open/closed principle: the element classes remain unchanged, and new operations can be added via new visitor implementations.
This pattern is particularly useful when dealing with objects that belong to different classes but share common operations, as it separates the algorithm from the object structure.
Key Components of the Visitor Pattern:
Visitor Interface: Declares a visit method for each type of element in the system.
Concrete Visitor: Implements the visit operations for each element type.
Element Interface: Defines an
accept()
method that takes a visitor as an argument.Concrete Elements: Implement the
accept()
method, which calls the appropriatevisit()
method on the visitor object.Client: Uses the visitor to apply different operations on the elements.
Code Example
Here’s an implementation of the Visitor Pattern using renamed class and function names for better understanding.
1. Visitor Interface
The ShapeVisitor
interface declares visit methods for different types of shapes.
// Visitor Interface
interface ShapeVisitor {
void calculate(CircleShape circle);
void calculate(SquareShape square);
}
2. Concrete Visitor
The AreaCalculatorVisitor
class implements the ShapeVisitor
interface and calculates the area of each shape.
// Concrete Visitor
class AreaCalculatorVisitor implements ShapeVisitor {
@Override
public void calculate(CircleShape circle) {
double area = Math.PI * circle.getRadius() * circle.getRadius();
System.out.println("Area of Circle: " + area);
}
@Override
public void calculate(SquareShape square) {
double area = square.getSide() * square.getSide();
System.out.println("Area of Square: " + area);
}
}
3. Element Interface
The GeometricShape
interface defines the accept()
method that will be used by the visitor.
// Element Interface
interface GeometricShape {
void accept(ShapeVisitor visitor);
}
4. Concrete Elements
The CircleShape
and SquareShape
classes implement the GeometricShape
interface and their respective accept()
methods.
// Concrete Element - Circle
class CircleShape implements GeometricShape {
private double radius;
public CircleShape(double radius) {
this.radius = radius;
}
public double getRadius() {
return radius;
}
@Override
public void accept(ShapeVisitor visitor) {
visitor.calculate(this); // Pass the CircleShape object to the visitor
}
}
// Concrete Element - Square
class SquareShape implements GeometricShape {
private double side;
public SquareShape(double side) {
this.side = side;
}
public double getSide() {
return side;
}
@Override
public void accept(ShapeVisitor visitor) {
visitor.calculate(this); // Pass the SquareShape object to the visitor
}
}
5. Client
The VisitorDemo
class demonstrates how to use the visitor pattern to calculate areas of different shapes.
// Client Class
public class VisitorDemo {
public static void main(String[] args) {
// Array of geometric shapes
GeometricShape[] shapes = {
new CircleShape(5.0),
new SquareShape(4.0)
};
// Create the visitor object
ShapeVisitor areaCalculator = new AreaCalculatorVisitor();
// Apply the visitor to each shape
for (GeometricShape shape : shapes) {
shape.accept(areaCalculator);
}
}
}
Explanation:
ShapeVisitor Interface: This interface defines two methods,
calculate(CircleShape circle)
andcalculate(SquareShape square)
, which are used to perform specific operations on the respective shape types.AreaCalculatorVisitor: This class implements the
ShapeVisitor
interface and defines the logic for calculating the area of a circle and a square.GeometricShape Interface: This interface contains an
accept()
method, which takes a visitor object and allows the shape to delegate operations to the visitor.CircleShape and SquareShape: These classes implement the
GeometricShape
interface. Theaccept()
method calls the appropriatecalculate()
method from the visitor.Client (VisitorDemo): The client creates a collection of shapes (
CircleShape
andSquareShape
) and applies theAreaCalculatorVisitor
to compute the area of each shape using theaccept()
method.
Benefits of the Visitor Pattern:
Open/Closed Principle: The pattern allows you to add new operations (via new visitors) without modifying the existing element classes. This makes the system open for extension but closed for modification.
Separation of Concerns: Visitor logic is separated from the element structure, leading to cleaner, more maintainable code. The visitor focuses on the operation, while the element focuses on its internal data.
Flexibility: You can introduce new visitors to perform different operations on the same elements without altering the element's structure or code.
Easy Maintenance: Since the logic for each operation is centralized in a visitor, it becomes easier to update and maintain operations related to the objects.
Real-World Use Cases:
Compilers: In compilers, the visitor pattern is used to traverse abstract syntax trees (AST) and apply operations such as type checking, code generation, or optimization on different nodes.
File Systems: Visitors can be used to perform operations such as searching, deleting, or compressing files in a file system.
UI Components: In UI frameworks, the visitor pattern can be used to apply various actions (e.g., rendering, resizing) on different components like buttons, text fields, or windows.
Conclusion:
The Visitor Pattern is a powerful design pattern that allows for adding new functionality to existing class hierarchies without modifying the classes themselves. By utilizing visitors, you can decouple operations from the data structures they operate on, promoting flexibility and adherence to the open/closed principle. This pattern is especially useful when you need to perform several unrelated operations across a collection of objects without altering their code.