Visitor pattern
Visitor pattern allows you to perform operation on group of objects. Group can be any type like collection of objects, hierarchical structure or composite structure. Algorithm of operation independent from group of objects.
problems
Suppose we have some data structure with nodes of different kinds. For example a city map. We want to perform some operations, such as how much money a tourist needs or how long it takes for a courier. These operations may need to treat each type of node differently.
We can add new fuctionality to every kind of node. But this wil spoil code. Why public statue must have property of cost for tourist? And should we change the nodes again for a new operation?
You can solve such problems if each operation will be implemented in a separate class called visitor. And the visitor will know how to work with each kind of node. Yes, there is a drawback. If a new kind of node is added, you will need to change all visitors.
advantages
- It is easy to add new operations without changing the visitable classes and the object group itself.
- Decouples an algorithm from the structure of a group of objects.
- We don't have a divine switch.
visitors vs iterators
Both patterns are used for traverse object structure, but
- iterators are used on collection of objects with same type. And visitors can be used with hierarchical structures or composite structures where objects are different types.
- client defines an operation when iterators are used. And visitor defines an operation for client, when visitors are used.
example
// interface for every visitable element
interface Visitable {
void accept(Visiter visiter);
}
// interface of visitor which perform operation for all kinds of elements
interface Visiter {
void visit(NodeEl1 v);
void visit(NodeEl2 v);
// ...
}
// concrete implementation of DemoVisitable
class NodeEl1 implements Visitable{
@Override
public void accept(Visitor visitor) {
visitor.visit(this);
}
...
}
// second concrete implementation of DemoVisitable
class NodeEl2 implements Visitable{
private List<NodeEl1> elements;
@Override
public void accept(Visitor visitor) {
for(NodeEl1 el: elements){
el.visit(visitor);
}
visitor.visit(this);
}
}
// visiter demo
class DemoVisiter implements Visiter {
@Override
void visit(NodeEl1 v){
// ... do stuff with NodeEl1
}
@Override
void visit(NodeEl2 v){
// ... do stuff with NodeEl2
}
}
// perform operation DemoVisiter
nodeEl2.accept(new DemoVisiter());