Почему посетитель отвечает за перечисление детей в схеме посетителей? - PullRequest
7 голосов
/ 07 февраля 2009

Исходя из кода, который я нашел, кажется, что посетитель должен знать структуру посещенных объектов и вызывать нужных потомков. Это кажется немного неуклюжим в некоторых случаях, когда посетитель захочет продолжить работу, даже если посещенные классы изменены.

Полагаю, реальный вопрос таков: Является ли их шаблон, в котором перечисление выполняется с помощью посещенного кода, а не кода посетителя?

Ответы [ 5 ]

12 голосов
/ 07 февраля 2009

Для объекта посетителя требуется , чтобы знать структуру посещаемых им объектов. Это нормально, хотя. Вы должны написать специальные операции посещения для каждого типа вещей, которые посетитель знает, как посетить. Это позволяет посетителю решить, сколько он действительно хочет посетить и в каком порядке.

Предположим, у вас есть дерево. Один посетитель может выполнить обход по предварительному заказу, другой - обход по порядку, а еще один посетитель может действовать только на конечные узлы. Классы посетителей могут делать все это без каких-либо изменений в древовидном классе.

Посетитель знает структуру, но это не обязательно означает, что операция, которую выполняет посетитель, знает всю структуру. Вы можете объединить посетителя с помощью команды . Дайте объекту посетителя объект команды, и посетитель будет вызывать команду для каждой вещи, которую он посещает.

Если вы хотите выполнить простую операцию и позволить коллекции дать вам каждый элемент для действия, тогда вы хотите, чтобы коллекция предоставила для себя iterator . Вызывайте свою функцию для каждой вещи, которую вам дает итератор.

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

3 голосов
/ 07 февраля 2009

Да. Посещенные объекты могут выполнять перечисление (то есть вызывать нужных потомков). Это все еще называется паттерном «посетитель» (на самом деле, Design Pattern * первый образец Visitor делает это так). Мой пример кода:

public void accept(Visitor visitor) {
  for (Node n : children) {
    n.accept(visitor);
  }
}

Примечание: для посещения детей мы не можем сказать visitor.visit(n);. Это связано с тем, что Java не выбирает метод динамически (на основе класса его аргументов во время выполнения), а выбирает метод статически (по типу аргументов во время компиляции).

2 голосов
/ 08 октября 2010

Иерархический шаблон посетителей объясняет другой подход, в котором добавляются события для входа и выхода из уровней. Связанная страница обсуждения представляет аргументы для итерации, находящиеся внутри посетителя или контейнера. Он включает в себя предложение использовать внешний итератор, который имеет смысл для меня, если у вас есть регулярное дерево и вам нужно выполнять итерации по-другому.

Оглядываясь назад на моего посетителя oofRep он имел ряд уровней для посещения различных классов и имел итерацию в таких методах, как:

void
oofRepVisitor::VisitViewHeaders(oofRepBandList& inBands)
{
    VisitBandList(inBands);
}


void
oofRepVisitor::VisitBandList(oofRepBandList& inBands)
{
    EnterLevel();
    const unsigned long numBands = inBands.count();
    for (unsigned long i=0; i<numBands; i++) {
        oofRepBand* theBand = inBands.value(i);
        assert(theBand);
        VisitTypedBand(theBand);
    }
    LeaveLevel();
}

с переопределением

void
OOF_repXMLlayoutVisitor::VisitViewHeaders(oofRepBandList& inBands)
{
    oofRepStreamEnv::out() << mIdentities.getIndentString();
    if (inBands.keepTogether())
        oofRepStreamEnv::out()  << "<header>\n";    
    else  // default is ON, and simplifies XML
        oofRepStreamEnv::out()  << "<header keepTogether='false'>\n";
    VisitBandList(inBands);
    oofRepStreamEnv::out() 
        << mIdentities.getIndentString()
        << "</header>\n";
}
2 голосов
/ 07 февраля 2009

Короче говоря, я думаю, что шаблон Visitor ортогонален способу перечисления. Это можно сделать любым способом или вообще без перечисления.

Я думаю, что посетитель должен знать, из каких элементов состоит структура посещаемого объекта. Хотелось бы знать, что машина состоит из колес и двигателя. Думаю, знать, как именно они сочетаются, не нужно. Рассмотрим следующий пример. Инсайдер знает структуру посещаемого объекта и сам выполняет перечисление. Аутсайдер не знает об этом и передает перечисление посещаемому объекту.

interface Visitable {
    void accept(Visitor visitor);
}

class WorkingRoom implements Visitable {
    public int number;
    WorkingRoom(int number) {
        this.number = number;
    }

    public void accept(Visitor visitor) {
        visitor.visit(this);
    }
}

class BossRoom implements Visitable {
    public String bossName;
    BossRoom(String bossName) {
        this.bossName = bossName;
    }
    public void accept(Visitor visitor) {
        visitor.visit(this);
    }
}

interface Visitor{
    void visit(WorkingRoom workingRoom);
    void visit(BossRoom bossRoom);
    void visit(Office office);
}

class Office implements Visitable{
    public Visitable[] firstFloor;
    public Visitable[] secondFloor;
    public Visitable ceoRoom;
    public Office(){
        firstFloor = new Visitable[]{ new WorkingRoom(101),
                                        new WorkingRoom(102),
                                        new BossRoom("Jeff Atwood"),
                                        new WorkingRoom(103)};
        secondFloor = new Visitable[]{  new WorkingRoom(201),
                                        new WorkingRoom(202),
                                        new BossRoom("Joel Spolsky")};

        ceoRoom = new BossRoom("Bill Gates");
    }

    public void accept(Visitor visitor) {
        visitor.visit(this);
    }

    public void showMeTheOffice(Visitor visitor, boolean sayPlease) {
        // Office manager decides the order in which rooms are visited
        for(int i=secondFloor.length-1; i >= 0; i--){
            secondFloor[i].accept(visitor);
        }
        if (sayPlease){
            ceoRoom.accept(visitor);
        }
        for (int i = 0; i < firstFloor.length; i++) {
            firstFloor[i].accept(visitor);
        }
    }
}

class Insider implements Visitor{
    public void visit(WorkingRoom workingRoom) {
        System.out.println("I> This is working room #"+workingRoom.number);
    }

    public void visit(BossRoom bossRoom) {
        System.out.println("I> Hi, "+bossRoom.bossName);
    }

    public void visit(Office office) {
        // I know about office structure, so I'll just go to the 1st floor
        for(int i=0;i<office.firstFloor.length;i++){
            office.firstFloor[i].accept(this);
        }
    }
}

class Outsider implements Visitor{

    public void visit(Office office) {
        // I do not know about office structure, but I know they have a 
        // nice office manager
        // I'll just ask to show me the office
        office.showMeTheOffice(this, true);
    }

    public void visit(WorkingRoom workingRoom) {
        System.out.println("O> Wow, room #"+workingRoom.number);
    }

    public void visit(BossRoom bossRoom) {
        System.out.println("O> Oh, look, this is "+bossRoom.bossName);
    }
}

public class Main{
    public static void main(String[] args) {
        Office office = new Office(); // visited structure
        // visitor who knows about office structure
        Insider employee = new Insider(); 
        office.accept(employee);
        System.out.println();
        // visitor who does not know about exact office structure
        // but knows something else
        Outsider candidate = new Outsider(); 
        office.accept(candidate);

        // no enumeration at all, but still a visitor pattern
        Visitable v = new BossRoom("Linus Torvalds");
        v.accept(candidate);
    }
}

У меня был проект с широким использованием шаблона посетителя без какого-либо перечисления. У нас был базовый интерфейс Field и многие классы, реализующие его, например StringField, NumberField и т. Д. Очень часто нам приходилось делать разные вещи в зависимости от типа поля, например, отображать его другим способом, загружать из БД, экспортировать в xml и т. Д. Мы могли бы определять методы в интерфейсе Field, но это позволило бы объединить его с каждой отдельной особенностью проекта - бедное поле должно знать об экспорте, импорте, рендеринге в html и rtf и т. Д. Мы также могли бы использовать instanceof, но набор возможных классы, реализующие интерфейс поля, со временем менялись, и было возможно добавить новый тип поля и забыть добавить

else if (field instanceof NewlyAddedFieldType) {...}

где-то. Поэтому мы решили использовать шаблон посетителя, и это было похоже на

Visitor v = new XMLExportVisitor(outputStream);
field.accept(v);

Поскольку любая реализация поля должна иметь метод

void accept(FieldVisitor visitor)

тогда, если я добавлю новую реализацию интерфейса Field, я должен как-то реализовать ее. Обычно это

visitor.visit(this);

где это недавно добавленный класс. Это заставляет меня добавить

void visit(NewlyAddedClass visited);

к интерфейсу FieldVisitor, что позволяет мне применять его в каждой реализации FieldVisitor, которая у нас уже есть. Поэтому, если я забуду что-то сделать, я получу ошибку компилятора. Перечисление в этом случае, если таковое имеется, было сделано за пределами посещенной структуры и посетителя. Но я все еще думаю об этом как о действительном случае шаблона посетителя. Это оказалось немного сложнее в реализации, но проще и безопаснее в использовании.

0 голосов
/ 07 февраля 2009

Посмотрите объяснения в этой статье .

Из Wiki :

В объектно-ориентированном программировании и разработка программного обеспечения, посетитель шаблон дизайна является способом разделения алгоритм из структуры объекта на котором он действует. Практический Результатом этого разделения является возможность добавлять новые операции в существующие объектные структуры без модифицируя эти структуры. Таким образом, использование шаблона посетителя помогает соответствие с открытым / закрытым принцип.

...