Дизайн для подражания посетителю без его недостатков - PullRequest
1 голос
/ 12 января 2012

Я ищу чистый дизайн для эмуляции функциональности Visitor без многих недостатков. В Java традиционные реализации (как описано в GoF) прибегают к двойной диспетчеризации, чтобы избавиться от if-elses. Чтобы решить эту проблему, я видел несколько реализаций, которые используют отражение, чтобы избежать изменений в классах «Visitable», но они полагаются на жестко закодированные строки при поиске имен методов. Хотя это довольно полезно, я все же думаю, что они не чистый дизайн.

Можно ли эмулировать ту же идею, используя структуры данных и / или хороший ОО-дизайн? Это не обязательно должен быть шаблон, я просто ищу примеры, где решается похожая проблема (например, с помощью Map<Class<T>,SomeFunctionObject>).


ОБНОВЛЕНИЕ Примерно так:

    public abstract class BaseVisitor<T> {

        private final TypesafeHeterogeneusMap map;

        protected BaseVisitor(){
            map = inflateFunctions();
        }   

        public <E extends T> void  process(E element){
            if(element == null){
                throw new NullPointerException();
            }
            boolean processed = false;

            @SuppressWarnings("unchecked")
            Class<? super T> sc = (Class<? super T>) element.getClass();

            while(true){            
                if(sc != null){
                    FunctionObject<? super T> fo2 = map.get(sc);
                    if(fo2 != null){
                        fo2.process(element);
                        processed = true;
                        break;
                    }
                    sc = sc.getSuperclass();
                } else {
                    break;
                }
            }

            if(!processed) System.out.println("Unknown type: " + element.getClass().getName());     
        }

        abstract TypesafeHeterogeneusMap inflateFunctions();
    }

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

Ответы [ 3 ]

3 голосов
/ 12 января 2012

Вы можете просто заставить все ваши реализации Visitor расширять базовый класс, который обеспечивает реализацию по умолчанию для каждого типа Visitable:

public interface AnimalVisitor {
    void visitHorse(Horse horse);
    void visitDog(Dog dog);
}

public class BaseAnimalVisitor implements AnimalVisitor {
    public void visitHorse(Horse horse) {
        // do nothing by default
    }
    public void visitDog(Dog dog) {
        // do nothing by default
    }
}

Затем, когда вводится новый класс Cat, вы добавляетеметод visitCat(Cat cat) для интерфейса и базового класса, и все посетители остаются неизменными и все еще компилируются.Если они не хотят игнорировать кошек, вы переопределяете метод visitCat.

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

Хотя это не тот ответ, который вы ищете: рассмотрите возможность использования языка более высокого уровня, менее подробного, чем Java.Вы обнаружите, что такие вещи, как шаблон посетителя, начинают казаться неактуальными.Конечно, если вы хотите определить логику для обхода структуры данных в одном месте, и определить, что делать с элементами структуры данных (на основе их типов) где-то еще, и сделать возможным смешивать и сопоставлять обход/ стратегии обработки, вы можете сделать это.Но вы можете сделать это с помощью небольшого количества простого кода, ничего такого, что вы могли бы назвать «шаблоном».

Я пришел из C / Java программирования и начал изучать различные динамические языки несколько лет.тому назад.Было невероятно понять, как много вы можете сделать в нескольких строках кода.

Например, если бы я должен был эмулировать шаблон Visitor в Ruby:

module Enumerable
  def accept_visitor(visitor)
    each do |elem|
      method = "visit#{elem.class}".to_sym
      elem.send(method,elem) if elem.respond_to? method
    end
  end
end

Объяснить: в Ruby Enumerable представляет все, что может быть повторено.В этих 8 строках кода я создал каждый вид объекта, который можно повторять для принятия посетителей.Планирую ли я, чтобы 5, 10 или 100 различных классов принимали Посетителей, эти 8 строк - все, что нужно.

Вот пример посетителя:

class CatCounter
  attr_reader :count
  def initialize; @count  = 0; end
  def visitCat;   @count += 1; end
end

Обратите внимание, что посетительНе нужно определять методы для all различных типов Visitable.Каждый посетитель просто должен определить методы для типов Visitable, которые его интересуют;он может игнорировать все остальное.(Это означает, что вам не нужно изменять кучу существующего кода, если вы добавляете новый тип Visitable.) И любой посетитель может взаимодействовать с любым объектом, который принимает посетителей.

Только в этих нескольких строках кода все проблемы, которые вы упомянули в шаблоне Visitor, были преодолены.

Не поймите меня неправильно;Ява - отличный язык для некоторых вещей.Но вам нужно выбрать правильный инструмент для работы.Тот факт, что вы так много боретесь, чтобы преодолеть ограничения своего инструмента, может указывать на то, что в этом случае требуется другой инструмент.

0 голосов
/ 24 января 2012

@ MisterSmith, так как вы должны использовать Java, и, вероятно, у вас есть веские причины для использования Visitor, я собираюсь предложить другое возможное решение.

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

«Логика обхода» может означать логику для обхода различных типов структур данных или для обхода одной и той же структуры данных в другом порядке. Или это может даже включать стратегии обхода, которые применяют определенные фильтры к возвращаемым элементам и т. Д.

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

Я предлагаю разделить шаблон Visitor на 3 , а не на 2 части:

  1. Объект Iterator, который реализует определенный обход

  2. Объект, который реализует стратегию «решения, что делать с элементом, основанным на его классе» (часть, которая обычно требует двойной отправки). Используя рефлексию, мы можем создать класс общего назначения, который делает это. Простая реализация использовала бы Map, или вы могли бы создать что-то, что генерирует байт-код динамически (я забыл метод платформы в Java, который позволяет загружать необработанные байт-коды как новый класс, но он есть). ИЛИ ЖЕ! ИЛИ, вы можете использовать динамический язык JVM, такой как JRuby или Clojure, чтобы написать # 2, скомпилировать в байт-код и использовать полученный файл .class. (Этот файл, вероятно, будет использовать байт-код invokedynamic, который, насколько я знаю, недоступен из Java - компилятор Java никогда не генерирует его. Если это изменилось, отредактируйте этот пост.)

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

Сохранение обхода в итераторе общего назначения позволяет вам делать с ним другие вещи (не только принимать посетителей).

Есть пара способов, которыми 3 части можно связать вместе; Я думаю, что # 2 обернет # 3 (принимая это как аргумент конструктора). # 2 предоставит открытый метод, который принимает Iterator в качестве аргумента и применяет к нему посетителя.

Интересная часть # 2. Я могу отредактировать этот пост позже, чтобы добавить пример реализации; сейчас у меня есть другие дела. Если кто-то еще придумает реализацию, пожалуйста, добавьте ее сюда.

...