2012-01-10 68 views
6

在维基百科sample和GoF书中,访客模式的使用通过在某个接受者上调用accept方法开始。但为什么这样呢?为什么我们不能开始用期望的acceptor作为参数调用visit方法?我们仍然可以根据访问者和接受者(双派遣)这两种类型制定访客行为 - 并且我们可以消除多余的呼叫(就我而言)。为什么我们通过调用Acceptor.accept()而不是Visitor.visit()来启动Visitor?

这里的示例代码来说明这一点:

public interface Visitor { 
    void visit(AcceptorA acceptor); 
    void visit(AcceptorB acceptor); 
} 

// 
// Visitor which sings 
// 
class SingingVisitor implements Visitor { 
    public void visit(AcceptorA acceptor) { 
     System.out.println("sing A"); 
    } 

    public void visit(AcceptorB acceptor) { 
     System.out.println("sing B"); 
    } 
} 


// 
// Visitor which talks 
// 
class TalkingVisitor implements Visitor { 
    public void visit(AcceptorA acceptor) { 
     System.out.println("talk A"); 
    } 

    public void visit(AcceptorB acceptor) { 
     System.out.println("talk B"); 
    } 
} 

// 
// Acceptor subclasses 
// 
class AcceptorA implements BaseAcceptor { 
} 

class AcceptorB implements BaseAcceptor { 
} 

// 
// Launcher class 
// 
class VisitorMain { 
    public static void main(String[] args) { 
     Visitor v = new TalkingVisitor(); 
     AcceptorA a = new AcceptorA(); 
     AcceptorB b = new AcceptorB(); 

     v.visit(a); 
     v.visit(b); 
     v = new SingingVisitor(); 
     v.visit(a); 
     v.visit(b); 
    } 
} 
+0

这只是一个例子 - 没有人说它必须以这种方式实施。 – 2012-01-10 19:43:42

+3

@DaveNewton我已经问过这个问题了,看看我是否忽略了GoF方法的某些优点,还是仅仅是一个约定。 – 2012-01-10 19:45:33

回答

5

考虑:

class House implements HouseAcceptor { 
    HouseAcceptor kitchen; 
    HouseAcceptor livingRoom; 

    void accept(HouseVisitor visitor) { 
     visitor.visit(this); 
     kitchen.accept(visitor); 
     livingRoom.accept(visitor); 
    } 
} 

class Kitchen implements HouseAcceptor { 
    void accept(HouseVisitor visitor) { 
     visitor.visit(this); 
    } 
} 

class LivingRoom implements HouseAcceptor { 
    void accept(HouseVisitor visitor) { 
     visitor.visit(this); 
    } 
} 

class SpeakingHouseVisitor implements HouseVisitor { 
    void visit(HouseAcceptor acceptor) { 
     System.out.println("Inside a HouseAcceptor"); 
    } 

    void visit(House acceptor) { 
     System.out.println("Inside a House"); 
    } 

    void visit(Kitchen acceptor) { 
     System.out.println("Inside a Kitchen"); 
    } 

    void visit(LivingRoom acceptor) { 
     System.out.println("Inside a LivingRoom"); 
    } 
} 

... 
HouseAcceptor acceptor = new House(); 
HouseVisitor visitor = new SpeakingHouseVisitor(); 

... 
// Doing it your way 
visitor.visit(acceptor); 
// Output: Inside a HouseAcceptor 

// Doing it the right way 
acceptor.accept(visitor); 
// Output: 
// Inside a House 
// Inside a Kitchen 
// Inside a LivingRoom 

需要注意的是,如果你做你的方式,你受的运行时类型不会有所作为:静态类型将被使用。通过执行双重调度,确保使用两种运行时类型。

2

因为Visitor■找不知道如何导航由Object的私人内部领域的知识。

如果你打电话给Visitor.visit(something)那么它将不得不弄清楚是否有某些需要横向的私人领域。要做到这一点,您需要something接受您的Visitor。一旦您决定导航必须位于访问对象中(而不是Visitor),那么您意识到需要回拨Visitor来告诉它导航路径中的下一个元素是什么。通常这是accept(...)方法;然而,如果你试图让accept(...)只是一个包装来启动导航(通过委派参数),那么你需要第二套方法来告诉Visitor你现在输入X,你现在输入Y.

通过使用GOF方法,人们可以安全地继承正在访问的项目修改访问路径以包含或跳过其他字段。这不会影响现有的Visitor,因为它们的界面不会改变。不需要重新编译Visitor的子类。

通过使用你的建议方法,当一个人将新类型添加到待访问项目的层次结构中时,需要重新编译所有访问者,即使访问者对新类型没有兴趣。

一个很好的折衷办法是:

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

都是你的“数据层次结构”实现的Visitable,和你的访问者有一个“简便方法”像这样

public abstract class Visitor { 

    public void initiate(Visitable v) { 
    v.accept(this); 
    } 

    public abstract void accept(...); 
    public abstract void accept(...); 
    public abstract void accept(...); 

} 

但是它给你如果有一个接口比这样的基类更好。对我而言,我倾向于更松散耦合的界面,但意见不同。

+0

-1您正在描述Visitor/Composite/Iterator组合。访问者模式也是有用的。 而你的妥协看起来更像是一个三重派遣给我Visitor-> Visitable-> Visitor。 – greyfairer 2012-01-10 20:13:23

+0

@greyfairer,我不是建议三重调度。我指出Visitable必须多次调用Visitor,并且如果您打算将“启动”放入Visitor中,它将成为一次便利函数。我不喜欢这个解决方案,但是我会说明它希望能够指出代码开销而没有额外的好处。至少Visitor-> Visitable调用只发生一次(但真的,为什么会浪费额外的堆栈框架?) – 2012-01-10 20:20:44

+0

如果您没有复合Visitable,Visitor模式也很有用。在这种情况下,Visitable只会调用访问者一次,但它会调用正确的重载方法。 – greyfairer 2012-01-10 20:28:29

5

使用版本,以下将无法编译:

List<BaseAcceptor> list = ... 
for(BaseAcceptor ba: list) 
    vi.visit(ba) 

Java编译器不能确定(静态)将是什么BA,所以它不能在编译的时候到店方法调用决定。您需要编写一个附加方法:

public void visit(BaseAcceptor ba){ 
    if(ba instanceof AcceptorA) 
    visit((AcceptorA)ba); 
    else if(ba instanceof AcceptorB) 
    visit((AcceptorB)ba); 
} 

这不是必需的使用访问者模式。

+0

如果我在'Visitor'接口中有“stub”'visit(BaseAcceptor)'方法,则第一个代码*将会被编译。但它仍然不会给我想要的行为,这是正确的。 – 2012-03-11 08:40:54

0

你没有双重调度。接受通常需要一个抽象的访客作为参数。

相关问题