假设我有一个复合层次结构来表示规则表达式,如下所示:
public abstract class Expression {
public abstract void accept(Visitor visitor);
}
public class Identifier extends Expression {
public final String token;
public Identifier(String token) {
this.token = token;
}
@Override
public void accept(Visitor visitor) {
visitor.visit(this);
}
public String getToken() {
return token;
}
}
public class Sequence extends Expression {
private final List<Expression> subExprs;
public Sequence(List<Expression> subExprs) {
this.subExprs = new ArrayList<Expression>(subExprs);
}
@Override
public void accept(Visitor visitor) {
visitor.visit(this);
}
public List<Expression> getSubExprs() {
return subExprs;
}
}
...
public abstract class Visitor {
public abstract void visit(Identifier identifier);
public abstract void visit(Sequence sequence);
}
问题是,如何实现需要爬树并递归计算结果的操作,例如:
例如,假设以下访问者实现:
public class Serialize extends Visitor {
public void visit(Sequence sequence) {
for (Expression subExpr : sequence.getSubExprs()) {
// here, I don't have any means to access the sub-results
subExpr.accept(visitor);
}
}
...
}
要在树的任何给定级别上计算结果,我需要知道以下级别的子结果。理想情况下,我需要用accept方法返回计算结果。但是,这似乎是不可能的,因为单个操作可能返回不同类型的结果。
我想到的唯一解决方案是在访问者类的地图中手动缓存子结果。但是,这似乎很麻烦。
在这种情况下,访客是否合适?什么是合适的实现?
最佳答案
首先,扩展访问者以返回结果。我经常做类似的事情:
public interface JSStatementVisitor<V, E extends Exception> {
public V visitBlock(JSBlock value) throws E;
public V visitVariable(JSVariableStatement value) throws E;
public V visitEmpty(JSEmptyStatement value) throws E;
...
}
这为您提供了更多的访客灵活性。如果不需要结果或例外,请使用
Void
和RuntimeException
。对应的是:
public interface JSStatement extends JSSourceElement {
public <V, E extends Exception> V acceptStatementVisitor(
JSStatementVisitor<V, E> visitor) throws E;
}
(通过这种方式,我更喜欢使用限定的访问方法(例如
visitBlock
而不是visit
或acceptStatementVisitor
而不是accept
),以避免在某些类必须实现多个接口的情况下发生命名冲突。另外,您可以使访问者成为有状态的对象,并使用类似
V getResult()
的方法,但是我不喜欢有状态的东西。这是第一部分。您的访问者现在返回值。
接下来,使用复合材料时,您需要汇总其结果(因为访问者仅需返回一个结果)。添加类似的方法:
public V aggregate(Iterable<V> values) throws E;
而且你在那里。
(我从未做过下面的事情,但是它很可能同样有效。)
还有一种选择是将一些
Callback<V>
回调函数传递给visit方法:public interface JSStatementVisitor<V, E extends Exception> {
public void visitBlock(JSBlock value, Callback<V> result) throws E;
}
因此,对应的是:
public interface JSStatement extends JSSourceElement {
public <V, E extends Exception> void acceptStatementVisitor(
JSStatementVisitor<V, E> visitor, Callback<V> result) throws E;
}
通过第一部分中的“另一个选择”,您甚至不需要其他聚合方法。您可以实现并传递
MyAggregatingCallback<V> implements Callback<V>
。它将汇总结果,然后允许将汇总的结果flush
V
到原始callback
。