正在跳过"接受"在哪里知道类型,访问者模式的有效优化?

时间:2015-05-15 12:11:27

标签: java visitor code-design

考虑以下访问者的简单语言解释器。

public interface Visitor{
    void visit( VarStat vs);
    void visit( Ident i);
    void visit( IntLiteral a);
    void visit( Sum s);
}

为了完整性,我添加了一些代码,提供了必要的实现细节(您可以跳过并直接阅读问题)。

public interface Visitable{
    void accept( Visitor v);
}

public class VarStat implements Visitable{
    Ident i;
    Exp   e;

    public VarStat(Ident id, Exp ex){
        i = id;
        e = ex;
    }

    public Ident getIdent() { return i; }
    public Exp getExp() { return e; }

    @Override
    public void accept( Visitor v){
        v.visit( this);
    }
}

public interface Exp extends Visitable{

}

public class Ident implements Exp{
    @Override
    public void accept( Visitor v){
        v.visit( this);
    }
}

var语句定义如下:

VarStat ::== var Ident = Exp;
Exp ::== Exp + Exp | IntLiteral | Ident
IntLiteral ::== [0-9]{0,8}
Ident ::== [a-zA-Z]+

有效的语言实例

var x = x+y+4;

表示VarStat节点的抽象方法如下:

.               _____VarStat _____
.              /       /  | \     \ 
.             /       /   |  \     \  
.            /       /    |   \     \
.         "var"   Ident  "="  Exp   ";"

问题

通常的VisitorPattern应用程序将是

void visit( VarStat vs){
     vs.getIdent().accept( this);
     vs.getExp().accept( this);
     //...
}
然而,因为我知道" Ident"属于Ident类型,可能的优化是

void visit( VarStat vs){

     visit( vs.getIdent());
     vs.getExp().accept( this);
     //...
}

这会跳过2个方法调用来提高性能(实际上它在我的场景中提供了很好的提升)。

这是否会被视为可能导致未来问题的设计错误?

3 个答案:

答案 0 :(得分:2)

在这种情况下,它不会是访客模式。并且如果它符合您的要求则不一定是访客经常被滥用并导致过度架构。

但是,您将失去潜在的好处。例如,在将调用转发给装饰/代理对象之前,您将无法为Ident创建装饰器或代理,并在accept方法中执行其他操作。

答案 1 :(得分:2)

访客只是一个复杂的脚手架,可以在Java等语言上实现双重调度。

当您处理叶子类型时,您不需要双重调度;运行时类型在编译时是已知的。直接调度叶子类型不仅是一种优化,更不合理。

当然,问题是,将来叶子类型可能会变成超级类型。使用IDE中的今天的重构工具,这不是一个大问题。

最好根据当前的要求制作简单的设计,而不是为未知的未来需求制作复杂的设计。

在java 8中,我们可以使用非常接近真实双重调度的语法实现双调度

final DoubleDispatch<Root,Void> dd = new DoubleDispatch<>();

dd.register(X.class, x->
{
    do something with x; its compile time type is X
    return null;
});
dd.register(Y.class, y->
{
    do something with y; its compile time type is Y
    return null;
});
// etc

...
dd.invoke( something );



// ----

public class DoubleDispatch<T, R>
{
    public R invoke(T obj){...}

    public <C extends T> void register(Class<C> type, Function<C,R> func){...}
}

另见 - Java Class.cast() and Overload

答案 2 :(得分:0)

会引起问题吗?很难说。 (我想如果语法改变了可能会引起意外......)

但我认为真正的问题是这是否是有价值的优化。具体来说,保存两个方法调用会在大局中产生显着差异吗?我的直觉是它不会。

这个翻译的表现真的很重要吗?

如果确实如此,为什么要在翻译中使用访问者模式?你不应该编译成中间虚拟机代码吗?还是字节码?