Design Patterns – Visitor
Introduction to the Visitor Pattern
The Visitor pattern is a behavioral design pattern that lets you separate algorithms from the objects on which they operate. It allows you to define a new operation without changing the classes of the elements on which it operates. This pattern is useful when you have a complex object structure and want to perform various operations on its elements without modifying the structure itself.
Key Components of the Visitor Pattern
The Visitor pattern involves the following key components:
- Visitor: The Visitor is an interface or an abstract class that defines a visit method for each type of element in the object structure. Concrete visitor classes implement these methods to provide the desired behavior.
- Element: The Element is an interface or an abstract class that declares an accept method. This method takes a visitor as a parameter, allowing the element to accept a visitor’s visitation request.
- Concrete Element: Concrete Element classes implement the accept method declared in the Element interface. They also provide the specific implementations for the accept method based on the visitor type.
- Object Structure: The Object Structure is a collection of elements that can be traversed by a visitor. It provides an interface to access the elements and may include various methods to manipulate the elements in the collection.
Implementing the Visitor Pattern
Let’s illustrate the Visitor pattern with a simple example in Java. We will create a visitor to calculate the total price of items in a shopping cart.
// Visitor
interface ShoppingCartVisitor {
double visit(Book book);
double visit(Fruit fruit);
}
// Element
interface ItemElement {
double accept(ShoppingCartVisitor visitor);
}
// Concrete Elements
class Book implements ItemElement {
private double price;
private String isbn;
public Book(double price, String isbn) {
this.price = price;
this.isbn = isbn;
}
public double getPrice() {
return price;
}
public String getIsbn() {
return isbn;
}
public double accept(ShoppingCartVisitor visitor) {
return visitor.visit(this);
}
}
class Fruit implements ItemElement {
private double price;
private String name;
public Fruit(double price, String name) {
this.price = price;
this.name = name;
}
public double getPrice() {
return price;
}
public String getName() {
return name;
}
public double accept(ShoppingCartVisitor visitor) {
return visitor.visit(this);
}
}
// Concrete Visitor
class ShoppingCartVisitorImpl implements ShoppingCartVisitor {
public double visit(Book book) {
double cost = book.getPrice();
if (book.getPrice() > 50) {
cost -= 5; // $5 discount on books over $50
}
return cost;
}
public double visit(Fruit fruit) {
return fruit.getPrice();
}
}
// Object Structure
class ShoppingCart {
private List<ItemElement> items = new ArrayList<>();
public void addItem(ItemElement item) {
items.add(item);
}
public double calculateTotal(ShoppingCartVisitor visitor) {
double total = 0;
for (ItemElement item : items) {
total += item.accept(visitor);
}
return total;
}
}
// Client
public class VisitorPatternExample {
public static void main(String[] args) {
ItemElement[] items = new ItemElement[] {
new Book(60, "1234"),
new Fruit(2, "Banana"),
new Fruit(3, "Apple")
};
ShoppingCartVisitor visitor = new ShoppingCartVisitorImpl();
ShoppingCart cart = new ShoppingCart();
for (ItemElement item : items) {
cart.addItem(item);
}
double totalPrice = cart.calculateTotal(visitor);
System.out.println("Total cost: $" + totalPrice);
}
}
In this example, we have a ShoppingCartVisitor
interface, concrete elements Book
and Fruit
, a concrete visitor class ShoppingCartVisitorImpl
, and an Object Structure
represented by the ShoppingCart
class. The client calculates the total cost of items in the shopping cart using the visitor pattern.
Usage of the Visitor Pattern
The Visitor pattern is useful when you need to perform operations on a collection of elements with varying types. It is often used in compilers, interpreters, and document processors to traverse and manipulate complex data structures.
Benefits of the Visitor Pattern
The Visitor pattern offers several advantages:
- Separation of Concerns: It separates the algorithm (visitor) from the object structure, making it easier to add new operations without modifying the elements.
- Extensibility: You can add new visitors without changing the existing elements or object structure.
- Type Safety: It ensures type safety by allowing visitors to define the specific behavior for each element type.
Comparison with Other Patterns
The Visitor pattern is often compared to the Strategy pattern, which also separates an algorithm from a class. However, the Visitor pattern focuses on operating on a collection of elements, while the Strategy pattern focuses on selecting an algorithm at runtime.
Conclusion
The Visitor pattern is a powerful tool for managing complex structures and operations. It allows you to define and extend operations on a group of elements without changing their classes, promoting flexibility and maintainability in Java and other object-oriented languages.