Represent an operation to be performed on the elements of an object structure. Define new operations without changing the classes of the elements on which it operates
Imagine that we have defined some classes which can be used to represent arithmetic expressions:
public interface Expression{}
public interface BinOp extends Expression{
Op operator();
Expression left();
Expression right();
}
public interface Val extends Expression{
int get();
}
Assuming we have concrete implementations for these interfaces representing multiplication, addition, etc., we could create arithmetic expressions as complex as we wish.
However, we also want to be able to actually perform operations on these expressions.
Suppose we want to evaluate the whole expression tree. We could add methods for this to the interfaces.
public interface Expression{
Val evaluate();
}
public class Mult implements BinOp{
private final Expression left;
private final Expression right
//... constructor, etc.
public Op operator(){return MULT;}
public Expression left(){return left;}
public Expression right(){return right;}
public Val evaluate(){
return left.evaluate * right.evaluate;
}
}
public class Value implements Val{
private final int value;
//...constructor, etc
public get(){return value};
public evaluate(){return get();}
}
This works, but it violates the principle of classes being closed to modification. It’s also likely that we will want to do a lot of different things with our expression trees (maybe some we haven’t imagined!), and we will need to modify the interfaces and implementations for each.
The visitor pattern allows us to create visitors that can be applied to the expression to perform the desired computation.
public interface Expression{
void accept(Visitor visitor);
}
public class Mult implements Expression{
//...
public void accept(Visitor visitor){
visitor.visitBinOp(this);
}
}
public class Value implements Val{
//...
public void accept(Visitor visitor){
visitor.visitValue(this);
}
}
public interface ExpressionVisitor {
void visitValue(Val value);
void visitBinOp(BinOp operator);
}
public class Evaluator implements ExpressionVisitor{
private final Stack<Integer> stack = new Stack<Integer>();
void visitValue(Val value){
stack.push(value.get());
}
void visitBinOp(BinOp operator){
operator.left().accept(this);
operator.right().accept(this);
int r = stack.pop();
int l = stack.pop();
int result = operator.op().apply(l,r);
stack.pop();
}
}
Now that the tree supports visitors, we are able to easily add an evaluation operator without touching the actual tree code. We can also add other visitors to perform other computations.
public class Printer implements ExpressionVisitor{
private final StringBuilder sb = new StringBuilder();
public void visitValue(Val value){
sb.append(value.get());
}
public void visitBinOp(BinOp op){
sb.append("(");
op.left().accept(this);
sb.append(op.toString);
op.right().accept(this);
sb.append(")");
}
}
public class MachineCoder implements ExpressionVisitor(){
private final StrinbBuilder sb = new StringBuilder();
public void visitValue(Val value){
sb.append("PUSH " + value.get() + "/n");
}
public void visitBinOp(BinOp op){
op.left().accept(this);
op.right().accept(this);
sb.append(op.toString() + "\n");
}
}
Decoupling the visitors and the structures is nice, but we pay the price when considering the cost of extending the structure if necessary.
If we add a new expression type, for instance an UnaryOp, then we have to add a visit method for this type. This will then require that every visitor be modified!
Another issue is that, since the visitor exists completely outside the structure it’s visiting, it necessarily requires that the elements of the structure expose their internal state so that the visitor can perform computations.
This goes against our general preference to encapsulate state.