1. Definition
The Visitor pattern is a behavioral design pattern that allows you to add further operations to objects without having to modify them. It involves a visitor class which provides a way to add new virtual functions to classes without modifying them.
2. Problem Statement
Consider a scenario where you have a structure of many distinct and unrelated objects, and you need to add operations to these objects without altering their classes. Making modifications in each class to add new operations can be inflexible and violate the open/closed principle.
3. Solution
The Visitor pattern suggests placing the new operation into a separate class called a visitor, rather than trying to integrate it into classes that already exist. Then, you declare a visit method in your object structure which accepts the visitor object as an argument. This way, the operation implementation can be externalized from the actual objects that it operates on.
4. Real-World Use Cases
1. A shopping cart system where various items can be visited and total cost calculated.
2. Syntax tree representation of a document where different nodes can be visited for rendering.
5. Implementation Steps
1. Declare an interface for the visitor, defining a visit method for each type of element that can have operations applied.
2. Implement concrete visitors that implement each operation.
3. In each element class, implement a method that takes a visitor and calls the visit method.
4. Client code creates visitor objects and passes it to elements via the visit method.
6. Implementation in Java
// Element interface
interface Element {
void accept(Visitor visitor);
}
// Concrete Elements
class Book implements Element {
private double price;
public Book(double price) {
this.price = price;
}
public double getPrice() {
return price;
}
@Override
public void accept(Visitor visitor) {
visitor.visit(this);
}
}
class DVD implements Element {
private double price;
public DVD(double price) {
this.price = price;
}
public double getPrice() {
return price;
}
@Override
public void accept(Visitor visitor) {
visitor.visit(this);
}
}
// Visitor interface
interface Visitor {
void visit(Book book);
void visit(DVD dvd);
}
// Concrete Visitor
class ShoppingCartVisitor implements Visitor {
private double totalCost = 0;
@Override
public void visit(Book book) {
totalCost += book.getPrice();
}
@Override
public void visit(DVD dvd) {
totalCost += dvd.getPrice();
}
public double getTotalCost() {
return totalCost;
}
}
// Client Code
public class Client {
public static void main(String[] args) {
Element book = new Book(20);
Element dvd = new DVD(15);
ShoppingCartVisitor shoppingCart = new ShoppingCartVisitor();
book.accept(shoppingCart);
dvd.accept(shoppingCart);
System.out.println("Total Cost = " + shoppingCart.getTotalCost());
}
}
Output:
Total Cost = 35.0
Explanation:
The Book and DVD classes represent elements that can be "visited". Each has a price attribute and an accept method to accept a visitor. The ShoppingCartVisitor is a concrete visitor that calculates the total cost of items. In the client code, elements are visited by the shopping cart, and the total cost is computed without modifying the element classes.
7. When to use?
1. When you need to perform operations on a complex object structure, and these operations should be decoupled from the objects themselves.
2. To clean up or implement operations outside the classes for which they are relevant.
3. When new operations are required to be added frequently, and the object structure is not expected to be changed but operations over them might change.