Как посетитель определяет новую операцию так, как этого не делает итератор?

Я знаю и часто использую как итераторы, так и посетители, и использовал и то, и другое еще до того, как услышал о шаблонах проектирования Gang of Four. Хотя синтаксис этих двух шаблонов сильно различается, я использую оба для одной и той же концептуальной цели: обхода группы объектов. Грубо говоря, я использую итератор, когда у меня есть неструктурированные объекты одного типа, и я использую посетителя, когда у меня есть структурированные объекты разных типов. Для меня посетитель — это просто элегантный, причудливый и сильно типизированный итератор.

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

Посетитель позволяет вам определить новую операцию без изменения классов элементов, над которыми она работает.

Итератор: предоставляет способ последовательного доступа к элементам агрегатного объекта, не раскрывая его базовое представление.

С тех пор я думал об этом и не могу понять, как посетитель определяет новую операцию.

Если я, например, хочу реализовать довольно простую операцию, например toLocalizedString() в качестве локализованной альтернативы toString(). Когда вы передаете посетителя элементу, он проходит через всю подструктуру этого объекта. Далее вы не можете ничего вернуть из методов accept/visit. Каждая из этих характеристик не позволяет мне использовать посетителя для определения toLocalizedString().

И в связи с этим у меня возникает вопрос: Как посетитель определяет новую операцию так, как это не делает итератор? Если верить описанию «Банды четырех», я чувствую, что упускаю самое главное. истинная сила шаблона посетителя.


person Tobber    schedule 20.06.2013    source источник


Ответы (2)


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

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

person Sergey Kalinichenko    schedule 20.06.2013

Вы определили следующие причины, по которым вы не можете ввести новые операции с посетителем:

  1. «Когда вы передаете посетителя элементу, он проходит через всю подструктуру этого объекта».
  2. «В дальнейшем вы ничего не можете вернуть из методов accept/visit».

Я думаю, что причина (1) часто вызывает серьезное беспокойство. Однако я думаю, что вы можете изменить своего посетителя, чтобы обойти это. Предположим, что вы заставляете visit и accept возвращать boolean, указывающее, следует ли продолжать исследование структуры. Затем вы можете заставить accept работать так (в псевдокоде):

 for (each child) {
     if (!child.accept(visitor)) return false;
 }
 return visitor.visit(this);

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

Однако для (2), я думаю, вы упускаете важную технику с посетителями - поскольку посетители являются объектами, они могут содержать конфиденциальную информацию о состоянии. Например, предположим, что я хочу написать простую функцию count, которая возвращает общее количество элементов в структуре. Тогда я мог бы написать что-то вроде этого:

public int count(Base object) {
    Visitor v = new Visitor() {
        private int numElems = 0;

        public void visit(Base object) {
            numElems++;
        }
        public void visit(Derived1 object) {
            numElems++;
        }
        // ... etc

        public int getCount() {
            return numElems;
        }
    };
    object.accept(v);
    return v.getCount();
}

Обратите внимание, что даже несмотря на то, что visit и accept здесь возвращают void, я все же могу создать общий метод, который возвращает значение, заставляя посетителя поддерживать внутреннее состояние и затем возвращая это состояние после завершения посещения. Отныне вызов count(obj) будет; возвращать количество всех объектов, по существу «прививая» дополнительный метод к иерархии классов.

Надеюсь это поможет!

person templatetypedef    schedule 20.06.2013
comment
Спасибо. Я знаю о булевом трюке, но по сути это просто if(..)break; в итераторе. Точно так же элемент count можно легко вычислить с помощью итератора, int count; for(each elem) count++; - person Tobber; 20.06.2013
comment
@Bittenus- я согласен. Моя цель состояла в том, чтобы показать, что ваши опасения по поводу ограничений шаблона посетителя были необоснованными. Вы можете представить себе гораздо более тонкую операцию, такую ​​как toLocalizedString, используя трюк с частным состоянием в сочетании с пользовательской реализацией получения локализованной строки для каждого типа. Если бы у вас был только итератор, вы не могли бы сделать это без написания гигантского каскада, если это такой-то тип, сделайте это, если это такой-то тип, сделайте то, ... Имеет ли это смысл? - person templatetypedef; 20.06.2013