It is up to the visitor implementation to decide whether to visit child nodes and in which order. That's the whole point of the visitor pattern.
In order to adapt the visitor for more situations it is helpful (and quite common) to use generics like this (it's Java):
public interface ExpressionNodeVisitor<R, P> {
R visitNumber(NumberNode number, P p);
R visitBinary(BinaryNode expression, P p);
// ...
}
And an accept
method would look like this:
public interface ExpressionNode extends Node {
<R, P> R accept(ExpressionNodeVisitor<R, P> visitor, P p);
// ...
}
This allows to pass additional parameters to visitor and retrieve a result from it. So, the expression evaluation can be implemented like this:
public class EvaluatingVisitor
implements ExpressionNodeVisitor<Double, Void> {
public Double visitNumber(NumberNode number, Void p) {
// Parse the number and return it.
return Double.valueOf(number.getText());
}
public Double visitBinary(BinaryNode binary, Void p) {
switch (binary.getOperator()) {
case '+':
return binary.getLeftOperand().accept(this, p)
+ binary.getRightOperand().accept(this, p);
// More cases for other operators here.
}
}
}
The accept
method parameter isn't used in the above example, but just believe me: it is quite useful to have one. For example, it can be a Logger instance to report errors to.
Avoid nesting logic into inner blocks as much as possible. It makes code harder to read.
if (!A)
return
if (B)
execute C
if (D)
execute E
Exit if not A. Otherwise B or D, but if you know D is already true when B is false. You could drop the if (D)
and just execute E.
In cases like this it's preferred to use a switch.
if (!A)
return
switch R
case B:
execute C
break
case D:
execute E
breal
Best Answer