有 Java 编程相关的问题?

你可以在下面搜索框中键入要查询的问题!

java如何在使用访问者模式时引用子结果?

假设我有一个复合层次结构来表示常规表达式,如下所示:

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);
}

问题是,如何实现需要爬树并递归计算结果的操作,例如:

  • 将regexp序列化为字符串
  • 将regexp求值为一组序列

例如,假设以下访问者实现:

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);
    }
  }

  ...
}

要计算树的任何给定级别的结果,我需要知道下面级别的子结果。理想情况下,我需要接受方法来返回计算结果。不过,这似乎是不可能的,因为单个操作可能会返回不同类型的结果

我想到的唯一解决方案是在visitor类的映射中手动缓存子结果。不过,这似乎很麻烦

在这种情况下,Visitor是合适的模式吗?什么是合适的实现


共 (1) 个答案

  1. # 1 楼答案

    首先,扩展访问者以返回结果。我经常这样做:

    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;
    
        ...
    
    }
    

    这给了你更多的灵活性。如果不需要结果或异常,只需使用VoidRuntimeException

    对应的是:

    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