Visitor Pattern

CSC-430

Phillip Wright

Visitor Pattern

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

Problem

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();
}

Problem II

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.

Evaluation

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;
  }
}

Evaluation II

public class Value implements Val{
  private final int value;
  //...constructor, etc
  public get(){return value};
  public evaluate(){return get();}
}

Problem

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.

Visitor

The visitor pattern allows us to create visitors that can be applied to the expression to perform the desired computation.

Visitor II

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);
  }
}

Visitor III

public interface ExpressionVisitor {
  void visitValue(Val value);
  void visitBinOp(BinOp operator);
}

Visitor IV

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();
  }
}

Visitor V

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.

Visitor VI

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(")");
  }
}

Visitor VII

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");
  }
}

Problems

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!

Problems II

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.