2014-12-20 2 views
3

Предположим, у меня есть составной иерархии представлять регулярные как выражения, например:Как ссылаться на субрезультаты при использовании шаблона посетителя?

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

Вопрос в том, как я могу осуществить операции, необходимые для обхода дерева и вычисления результатов рекурсивно, например:

  • сериализации регулярное выражение для строки,
  • оценить регулярное выражение к набору последовательностей,
  • ...

Предположим, например, следующую реализацию Visitor:

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

    ... 
} 

Чтобы вычислить результат при любом заданном уровне дерева, мне нужно знать, суб-результаты на нижних уровнях. В идеале мне нужен метод для возврата вычисленного результата. Однако это не представляется возможным, поскольку отдельные операции могут возвращать результаты другого типа.

Единственное решение, которое приходит мне на ум, заключается в том, чтобы кэшировать субрезультаты вручную на карте класса посетителей. Это кажется очень громоздким.

Является ли посетителем подходящий шаблон в этом случае? Что было бы подходящей реализацией?

+0

Вы можете сделать своих посетителей строителями и получить окончательный результат, когда они будут сделаны. –

ответ

4

Прежде всего, распространите посетителя, чтобы вернуть результаты. Я часто делаю что-то вроде:

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> обратного вызова метода визита:

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.