Почему посетитель несет ответственность за перечисление детей в шаблоне посетителя?

StackOverflow https://stackoverflow.com/questions/522803

Вопрос

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

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

Это было полезно?

Решение

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

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

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

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

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

Другие советы

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

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

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

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

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

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 и т. д.Мы могли бы определить методы в интерфейсе поля, но это сделало бы его связанным с каждой отдельной функцией проекта — плохое поле должно знать об экспорте, импорте, рендеринге в html и rtf и т. д.Мы также могли использовать instanceof, но набор возможных классов, реализующих интерфейс Field, со временем изменился, и можно было добавить новый тип поля и забыть добавить

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, которая у нас уже есть.Так что если я забуду что-то из этого сделать, то получу ошибку компилятора.Перечисление в данном случае, если таковое имелось, производилось вне посещаемой структуры и посетителя.Но я все еще думаю об этом как о действительном примере поведения посетителей.Это оказалось немного сложнее реализовать, но проще и безопаснее в использовании.

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

Оглядываясь назад на мой 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";
}

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

От Вики:

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top