Основная причина, по которой шаблон посетителя определяется в книге GoF как таковой, заключается в том, что в C ++ не было никакой формы идентификации типов во время выполнения (RTTI). Они использовали «двойную диспетчеризацию», чтобы заставить целевые объекты сказать им, какой у них был тип. Довольно круто, но невероятно сложно описать трюк.
Основное различие между тем, что вы описываете, и шаблоном GoF Visitor (как вы упоминаете) заключается в том, что у вас есть явный метод "dispatch" - метод "visit", который проверяет тип аргумента и отправляет его явному visitFoo методы, visitBar и др.
Шаблон GoF Visitor использует объекты данных для выполнения диспетчеризации, предоставляя метод «accept», который поворачивает и передает «this» обратно посетителю, разрешая правильный метод.
Чтобы сложить все это в одном месте, базовый шаблон GoF выглядит так (я парень по Java, поэтому, пожалуйста, извините код Java вместо C # здесь)
public interface Visitor {
void visit(Type1 value1);
void visit(Type2 value2);
void visit(Type3 value3);
}
(обратите внимание, что этот интерфейс может быть базовым классом с реализациями метода по умолчанию, если хотите)
и ваши объекты данных все необходимо реализовать метод "accept":
public class Type1 {
public void accept(Visitor v) {
v.visit(this);
}
}
Примечание. Большая разница между этим и тем, что вы упомянули для версии GoF, заключается в том, что мы можем использовать перегрузку метода, поэтому имя метода "посещения" остается согласованным. Это позволяет каждому объекту данных иметь идентичную реализацию «accept», что снижает вероятность опечатки
Каждый тип нуждается в одинаковом коде метода. «This» в методе accept приводит к тому, что компилятор разрешает правильный метод посещения.
После этого вы можете реализовать интерфейс Visitor, как захотите.
Обратите внимание, что добавление нового типа (например, Type4) в тот же или другой пакет потребует меньше изменений, чем вы описали. Если в том же пакете мы добавим метод в интерфейс Visitor (и каждую реализацию), но вам не нужен метод dispatch.
Это говорит ...
- Реализация GoF требует взаимодействия / модификации объектов данных. Это главное, что мне не нравится в этом (кроме попытки описать это кому-то, что может быть довольно болезненным. У многих людей возникают проблемы с концепцией «двойной отправки»). Я предпочитаю хранить свои данные и то, что я собираюсь делать с ними отдельно - подход типа MVC.
- Как ваша реализация, так и реализация GoF требуют изменения кода для добавления новых типов - это может нарушить существующие реализации посетителей
- Ваша реализация и реализация GoF являются статическими; «что делать» для определенных типов нельзя изменить во время выполнения
- Теперь у нас есть RTTI на языках, которые используются наиболее часто
Кстати, я преподаю Шаблоны проектирования в Джонсе Хопкинсе, и я бы хотел порекомендовать вам приятный динамический подход.
Начните с более простого однообъектного интерфейса Visitor:
public interface Visitor<T> {
void visit(T type);
}
Затем создайте VisitorRegistry
public class VisitorRegistry {
private Map<Class<?>, Visitor<?>> visitors = new HashMap<Class<?>, Visitor<?>>();
public <T> void register(Class<T> clazz, Visitor<T> visitor) {
visitors.put(clazz, visitor);
}
public <T> void visit(T thing) {
// needs error checks, and possibly "walk up" to check supertypes if direct type not found
// also -- can provide default action to perform - maybe register using Void.class?
@SuppressWarnings("unchecked")
Visitor<T> visitor = (Visitor<T>) visitors.get(thing.getClass());
visitor.visit(thing);
}
}
Вы бы использовали это как
VisitorRegistry registry = new VisitorRegistry();
registry.register(Person.class, new Visitor<Person>() {
@Override public void visit(Person person) {
System.out.println("I see " + person.getName());
}});
// register other types similarly
// walk the data however you would...
for (Object thing : things) {
registry.visit(thing);
}
Это позволяет вам теперь регистрировать независимых посетителей для каждого типа, который вы хотите посетить, и не будет нарушать существующие реализации посетителей, когда добавляется новый тип.
Вы также можете перерегистрировать (и отменить) различные комбинации посетителей во время выполнения, даже загружая определения того, что делать из некоторой информации о конфигурации.
Надеюсь, это поможет!