A typical example is the definition of operations on an Abstract Syntax Tree. Here is Java code using a Visitor:
package syntax;
abstract class ExpressionVisitor
{
abstract void visitIntExp(IntExp e);
abstract void visitAddExp(AddExp e);
}
abstract class Expression
{
abstract void accept(ExpressionVisitor v);
}
class IntExp extends Expression
{
int value;
void accept(ExpressionVisitor v)
{
v.visitIntExp(this);
}
}
class AddExp extends Expression
{
Expression e1, e2;
void accept(ExpressionVisitor v)
{
v.visitAddExp(this);
}
}
The interest of this construction is that
it is now possible to define operations on expressions
by subclassing ExpressionVisitor.
This can even be done in a different package,
without modifying the expression hierarchy classes.
// Behaviour can now be defined on Expressions
package tools;
class PrettyPrint extends ExpressionVisitor
{
void visitIntExp(IntExp e)
{
System.out.print(e.value);
}
void visitAddExp(AddExp e)
{
e.e1.accept(this);
System.out.print(" + ");
e.e2.accept(this);
}
}
Without visitors, the classes have to be modified each time a new
operations is added.
In this case, a prettyPrint member method
would be added to each class.
Another possibility would be to define a static method
in the new package. But then it would be necessary to test
the argument with instanceof and use downcasts.
In short, lose the benefits of object-orientation.
package syntax;
abstract class Expression { }
class IntExp extends Expression
{
int value;
}
class AddExp extends Expression
{
Expression e1, e2;
}
// Behaviour can now be defined on Expressions
package tools;
void prettyPrint(Expression e);
prettyPrint(IntExp e)
{
System.out.print(e.value);
}
prettyPrint(AddExp e)
{
prettyPrint(e.e1);
System.out.print(" + ");
prettyPrint(e.e2);
}
The Visitor pattern is a trick to introduce multiple dispatch in
a language that lacks it. However, it raises serious issues. Language support
for multi-methods makes it much easier and elegant to handle the common
situation where the Visitor pattern applies.