是否已知类型已知的"接受",访问者模式的有效优化?

Gam*_*per 8 java visitor code-design

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

public interface Visitor{
    void visit( VarStat vs);
    void visit( Ident i);
    void visit( IntLiteral a);
    void visit( Sum s);
}
Run Code Online (Sandbox Code Playgroud)

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

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

var语句定义如下:

VarStat ::== var Ident = Exp;
Exp ::== Exp + Exp | IntLiteral | Ident
IntLiteral ::== [0-9]{0,8}
Ident ::== [a-zA-Z]+
Run Code Online (Sandbox Code Playgroud)

一个有效的语言实例

var x = x+y+4;
Run Code Online (Sandbox Code Playgroud)

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

.               _____VarStat _____
.              /       /  | \     \ 
.             /       /   |  \     \  
.            /       /    |   \     \
.         "var"   Ident  "="  Exp   ";"
Run Code Online (Sandbox Code Playgroud)

问题

通常的VisitorPattern应用程序将是

void visit( VarStat vs){
     vs.getIdent().accept( this);
     vs.getExp().accept( this);
     //...
}
Run Code Online (Sandbox Code Playgroud)

但是,因为我知道"Ident"是Ident一种可能的优化类型

void visit( VarStat vs){

     visit( vs.getIdent());
     vs.getExp().accept( this);
     //...
}
Run Code Online (Sandbox Code Playgroud)

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

这被认为是一个可能导致未来问题的设计错误吗?

Zho*_*gYu 2

Visitor只是一个复杂的脚手架,用于在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){...}
}
Run Code Online (Sandbox Code Playgroud)

另请参阅 - Java Class.cast() 和 Overload