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

Posted

技术标签:

【中文标题】使用访问者模式时如何引用子结果?【英文标题】:How to reference sub-results when using visitor pattern? 【发布时间】:2014-12-20 21:12:01 【问题描述】:

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

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 方法来返回计算结果。不过,这似乎是不可能的,因为个别操作可能会返回不同类型的结果。

我想到的唯一解决方案是将子结果手动缓存在访问者类的地图中。不过,这似乎很麻烦。

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

【问题讨论】:

您可以让您的访问者成为建设者,并在他们完成后得到他们的最终结果。 【参考方案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 而不是visitacceptStatementVisitor 而不是accept)以避免可能的命名冲突,以防某些类必须实现多个接口。

或者你可以让你的访问者有状态并且有一个像V getResult()这样的方法,但我不是有状态的东西的忠实粉丝。

这是第一部分。您的访问者现在返回值。

接下来,在使用复合材料时,您需要汇总它们的结果(因为您的访问者只需要返回一个结果)。添加一个方法,如:

public V aggregate(Iterable<V> values) throws E;

你就在那里。


(我从来没有做过下面的事情,但它很可能会同样好用。)

另一种选择是将一些Callback&lt;V&gt; 回调函数传递给访问方法:

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&lt;V&gt; implements Callback&lt;V&gt;。它将聚合结果,然后允许 flush 聚合结果 V 到原始 callback

【讨论】:

以上是关于使用访问者模式时如何引用子结果?的主要内容,如果未能解决你的问题,请参考以下文章

是的,子模式访问父值(或将值传递给子值)

正则表达式之基础

如何使用子元素或引用改进 XML 模式?

js设计模式之单例模式实例

设计模式之代理模式

'var_name'未声明。由于其保护级别,它可能无法访问。在调试模式下