Represent an operation to be performed on the elements of an object structure.
Visitor lets you define a new operation without changing the classes
of the elements on which it operates.
Skip to Sample Code
The idea is to have two class hierarchies
- One for the elements being operated on, where each element has an "accept" method that takes a visitor object as an argument
- One for the visitors that define operations on the elements. Each visitor has a visit() method for each element class.
- Visitor: Declares the visit method.
- ConcreteVisitor: An implementation of the Visitor interface. May also store state if required.
- Element (or Visitable): The interface that declares the accept method. The accept method invokes the visit method passing itself as an argument.
- ConcreteElement: Element of the object structure. Has to implement accept method (implements Element).
When to Use
Use the Visitor pattern when
- There is a need perform operations that depend on concrete classes of an object structure, and the structure may contain classes of objects with differing interfaces.
- Distinct and unrelated operations must be performed on objects in an object structure, and you want to avoid distributing/replicating similar operations in their classes
- The classes defining the object structure rarely change, but new operations may be added every once in a while.
Pros and Cons
- Easy to add new operations: To add a new operation, you only have to add a new Visitor implementation. There is no need to change the element classes.
- Gather related operations: Visitor pattern helps gather related operations into the visitor, while the unrelated behavior is implemented in the individual elements.
- Visiting across class hierarchies: Unlike iterators, visitors may visit objects in an object structure which need not have objects of the same type.
- State Management: Visitors can keep track of state changes with each visit. Without a visitor, state has to be passed as an argument.
- Hard to add new concrete elements: Adding a ConcreteElement involves adding a new operation to the Visitor interface and a corresponding implementation in each concrete visitor implementation. Visitor pattern is best used in cases where the object structure is stable but the set of operations may change frequently.
- Breaking Encapsulation: Visitor pattern often forces you to provide public operations to access the internal state of the elements, which compromises encapsulation.
Visitor Pattern and Double Dispatch
Double dispatch is a mechanism that allows a function call to change depending on the runtime types of multiple objects involved in the call. In single dipatch a call like Integer.compareTo(Object o), the actual function call depends only on the calling object (the Integer object here). In double dispatch, the actual call may also depend on the object being passed as a parameter to the compareTo method.
The most common programming languages (except for LISP) do not have a way for implementing double dispatch. But you may implement double dispatch in these programming languages using the Visitor pattern. You can see the implementation in the following example. Here the call to accept depends not only on the type of object on which it is called (MyLong or MyInteger) but also on the parameter that is being passed to it (AddVisitor and SubtractVisitor).
package visitor;
interface Visitor {
public int visit(MyInteger wheel);
public int visit(MyLong engine);
}
interface Visitable {
public int accept(Visitor visitor);
}
class MyInteger implements Visitable {
private int value;
MyInteger(int i) {
this.value = i;
}
public int accept(Visitor visitor) {
return visitor.visit(this);
}
public int getValue() {
return value;
}
}
class MyLong implements Visitable {
private long value;
MyLong(long l) {
this.value = l;
}
public int accept(Visitor visitor) {
return visitor.visit(this);
}
public long getValue() {
return value;
}
}
class SubtractVisitor implements Visitor {
int value;
public SubtractVisitor(int value) {
this.value = value;
}
public int visit(MyInteger i) {
System.out.println("Subtract integer");
return (i.getValue() - value);
}
public int visit(MyLong l) {
System.out.println("Subtract long");
return ((int) l.getValue() - value);
}
}
class AddVisitor implements Visitor {
int value;
public AddVisitor(int value) {
this.value = value;
}
public int visit(MyInteger i) {
System.out.println("Adding integer");
return (value + i.getValue());
}
public int visit(MyLong l) {
System.out.println("Adding long");
return (value + (int) l.getValue());
}
}
public class VisitorTest {
public static void main(String[] args) {
AddVisitor cv = new AddVisitor(10);
SubtractVisitor sv = new SubtractVisitor(10);
MyInteger i = new MyInteger(20);
MyLong l = new MyLong(20);
System.out.println(i.accept(cv));
System.out.println(l.accept(cv));
System.out.println(i.accept(sv));
System.out.println(l.accept(sv));
}
}
No comments:
Post a Comment