The visitor classes we have seen (such as Evaluator
and Converter
)
have methods with different names: visitSum
, which takes an
argument that is a Sum
, visitProduct
, which takes an argument that is
a Product
, and visitConstant
, which takes a Constant
. Would it be
possible to shorten the name of all three methods to just
visit
? For example, could the three methods in the
Evaluator
class be defined as shown below, provided that
we correspondingly changed the places where they are called?
public Integer visit(Sum s){ return s.getLeft().accept(this) + s.getRight().accept(this); } public Integer visit(Product p){ return p.getLeft().accept(this) * p.getRight().accept(this); } public Integer visit(Constant c){ return c.getValue(); }
The answer to this question is "yes". In Java, as well as many
other languages, a class can have multiple methods that have the same
name, so long as the argument types suffice to distinguish them. This
feature is known as overloading. Inside the Sum
class's accept
method, where this
is known
to be of type Sum
, the method invocation
v.visit(this)
can unambiguously be understood as invoking
the visit
method that has a Sum
argument,
just as much so as when that method was named visitSum
.
I usually prefer not to use overloading because it often detracts from clarity and because it can make bugs harder to track down. However, even if you never use it, you need to understand it. It also leads naturally into our next topic, multimethods.
Supposing you rename all the visiting methods to
visit
, as shown above, it is tempting to think that the
accept
methods in the AST classes become unnecessary.
For example, couldn't we rewrite the Evaluator
class's
method for visit
ing a Sum
as follows?
public Integer visit(Sum s){ return this.visit(s.getLeft()) + this.visit(s.getRight()); }
This code looks right, but it won't work. In fact, it will cause a
compile-time error, because there is no visit
method that
takes an argument of type Expr
, which is the type (from
the compiler's static perspective) of s.getLeft()
and
s.getRight()
. The fact that each runtime object that is
a left or right operand will always in fact be either a
Sum
, a Product
, or a Constant
doesn't help any, even though there are visit
methods
accepting each of these. The compile-time error can be made to go
away by adding one more method to the Evaluator
class:
public Integer visit(Expr e){ throw new Error("Unknown kind of Expr: " + e); }
However, if you try running this program, you will find that the
error message is always produced. The compiler selects the
appropriate one of the overloaded visit
methods by using
only the static type of the argument. The dynamic type of object
passed at runtime has no influence, unlike the type of object before
the dot. We say that Java uses single dispatch: only the type
of one object (the one before the dot) influences the runtime method
dispatching. This explains why the visitor pattern needs to use both
the accept
methods in the AST classes and the
visit
methods in the visitor classes. By using single
dispatching twice (known as double dispatching), both the type
of the AST node object and the type of the visitor can influence the
code that runs.
If we are willing to go outside of Java and look at other
programming languages, then something like the simplified version can
work, and we won't need the accept
methods. For example,
in the MultiJava language (which is an extension of Java), one could
write the methods in Evaluator
to look like this:
public Integer visit(Expr@Sum s){ return this.visit(s.getLeft()) + this.visit(s.getRight()); }
This visit
method applies in situations where the
argument's static type is Expr
, but where the dynamic
type of the actual object passed at runtime turns out to be
Sum
. This feature is known as multiple dispatch.
Methods that use multiple dispatch are known as multimethods.
In a language with multimethods, you can declare a visit
method that runs when the object doing the visiting happens to be an
Evaluator
and the object being visited happens to be a
Sum
. The above MultiJava method would do this, assuming
it appears within the Evaluator
class.