Почему мы запускаем Visitor, вызывая Acceptor.accept (), а не Visitor.visit ()? - PullRequest
6 голосов
/ 10 января 2012

В Википедии sample и в книге GoF использование шаблона Visitor начинается с вызова метода accept на некотором акцепторе. Но почему это так? Почему мы не можем начать вызывать метод visit с желаемым акцептором в качестве аргумента? Мы все еще можем сделать поведение посетителя зависимым от 2 типов - посетителя и получателя (двойная отправка) - и мы можем устранить избыточный вызов (как мне кажется).

Вот пример кода, чтобы проиллюстрировать это:

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

Ответы [ 4 ]

5 голосов
/ 11 января 2012

Учтите:

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

Обратите внимание, что если вы сделаете это по-своему, тип времени выполнения вашего акцептора не будет иметь значения: будет использоваться статический тип.Делая двойную диспетчеризацию, вы гарантируете, что используются оба типа времени выполнения.

5 голосов
/ 10 января 2012

Используя вашу версию, следующее не скомпилируется:

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

Компилятор Java не может определить (статически), какой будет ба, поэтому он не может решить во время компиляции, какой метод посещения вызвать. Вам нужно будет написать дополнительный метод:

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

Это не обязательно при использовании шаблона посетителя.

2 голосов
/ 10 января 2012

Поскольку Visitor s не знает, как перемещаться по закрытым внутренним полям составного Object.

Если бы вы позвонили Visitor.visit(something), то пришлось бы выяснить, было ли это что-то частнымполя, которые нуждались в поперечном.Для этого вам нужно, чтобы something принял ваш Visitor.Как только вы решите, что навигация должна быть в посещенных объектах (а не в Visitor), вы поймете, что вам нужен обратный вызов к Visitor, чтобы сообщить ему, каков следующий элемент в пути навигации.Обычно это метод accept(...);однако, если вы попытались сделать accept(...) просто оболочкой для инициирования навигации (путем делегирования к параметру), тогда вам понадобится второй набор методов, чтобы сообщить Visitor, что вы вводите X сейчас, а вводу Y сейчас.

Используя подход GOF, можно безопасно разделить посещаемый элемент на подклассы и изменить путь посещения, чтобы включить или пропустить дополнительные поля.Это не повлияет на существующие Visitor s, потому что их интерфейс не изменится.Нет необходимости перекомпилировать подклассы 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 голосов
/ 11 января 2012

у вас нет двойной отправки. accept обычно принимает абстрактного посетителя в качестве аргумента.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...