Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why do we start Visitor by calling Acceptor.accept(), and not Visitor.visit()?

In Wikipedia sample and in GoF book, usage of Visitor pattern is started by calling accept method on some acceptor. But why is it this way? Why can't we start calling visit method with desired acceptor as an argument? We can still make visitor behavior depend on 2 types -- of visitor and of acceptor (double dispatch) -- and we can eliminate redundant call (as it seems to me).

Here's sample code to illustrate this:

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);
    }
}
like image 386
Victor Sorokin Avatar asked Jan 10 '12 19:01

Victor Sorokin


2 Answers

Consider:

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

Note that if you do it your way, the runtime type of your acceptor will not make a difference: the static type will be used. By doing double dispatch you ensure that both runtime types are used.

like image 59
gpeche Avatar answered Sep 28 '22 13:09

gpeche


Using your version, the following will not compile:

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

The java compiler cannot determine (statically) what ba will be, so it cannot decide at compile time which visit method to call. You would need to write an additional method:

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

This is not necessary using the visitor pattern.

like image 43
GeertPt Avatar answered Sep 28 '22 12:09

GeertPt