Recall our definition for an arithmetic expression without variables:
ArithExpr := Const(int) | Sum(ArithExpr, ArithExpr) ....
We would like to define operations on ArithExprs in such a way that we don't need to modify the class definition every time a new operation is required. To do so, we will use the Visitor Pattern. The idea is to extract the methods that define the operation for the various concrete subclasses and aggregate them as members of a new class especially defined to hold them. Such a class is called a visitor class and instances of such a class are called vistiors.
First, we will define a new abstract class Visitor that specifies what methods must be included in every visitor class for ArithExpr:
abstract class Visitor {
abstract int forConst(Const c);
abstract int forSum(Sum s);
...
}
Notice that each method takes an instance of the class it is intended for. This is needed to give it access to all the information that would be available if the method were defined inside that class, e.g., the values of the object's fields.
Now we will create a new concrete class EvalVisitor to hold all the methods for evaluation of an ArithExpr:
class EvalVisitor extends Visitor {
int forConst(Const c) {
return c.value;
}
int forSum(Sum s) {
return s.left.accept(this) + s.right.accept(this);
}
...
}
We need to install a hook in
each subclass of ArithExpr to execute the corresponding
visitor method. The hook is a new method, accept,
which takes a visitor as an
argument and calls the appropriate method in that visitor.
abstract class ArithExpr {}
abstract int accept(Visitor v);
}
class Const {
...
int accept(Visitor v) {
return v.forConst(this);
}
}
class Sum {
...
int accept(Visitor v) {
return v.forSum(this);
}
...
To evaluate an arithmetic expression, we simply call a.accept(new EvalVisitor()). If we wish to add more operations to arithmetic expressions, we can define new visitor classes to hold the methods, but there is no need to modify the existing subclasses of ArithExpr.
Notice that, since a visitor has no fields, all instances of a particular visitor class are identical. So it is wasteful to create new instances of the visitor every time we wish to pass it to an accept method. One way that we can eliminate this waste is to place a static field in the visitor class containing an instance of that class.
class EvalVisitor {
static only = new EvalVisitor();
...
}
Then, instead of accept(new EvalVisitor()), we may simply write accept(EvalVisitor.only).
An Aside on Anonymous Classes: An alternative to defining a concrete visitor class with a static field containing the sole instance of the visitor is to bind a variable as an anonymous class. As the name suggests, anonymous classes have no name and a unique instance, and therefore free us from the burden of coming up with new names for classes that will only have one instance. We can create one with a special expression that returns it as a value. Recall that an anonymous class has the following syntax:
new className( arg1, ..., argm) { member1, ..., membern }In most cases, the class className is either an abstract class or an interface, but it can be any class. The argument list arg1, ..., argm is used to call the constructor for the class className; if className is an interface, the argument list must be empty.
For example, to create an instance of a visitor that evaluates an arithmetic expression, we write:
new Visitor() {
int forConst(Const c) {...}
int forSum(Sum s) {...}
...
}
Since we generally want to use a visitor more than once, we usually bind the anonymous class instance to a variable, so we can access it again! The statement:
visitor ev = new Visitor() {
int forConst(Const c) {...};
int forSum(Sum s) {...};
...
};
binds the variable ev to our anonymous class instance.