Когда я должен использовать шаблон дизайна посетителя? [закрыто]

315

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

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

Джордж Мауэр
источник
7
Наконец-то получил его после прочтения этой статьи Джерми Миллера о моей ежевике, когда застрял в холле в течение двух часов. Это долго, но дает замечательное объяснение двойной отправки, посетителя и составного, и что вы можете сделать с ними.
Джордж Мауэр
1
вот хорошая статья: codeproject.com/Articles/186185/Visitor-Design-Pattern
Сейед Morteza Мусави
3
Шаблон посетителя? Который из? Дело в том, что в этом шаблоне дизайна много недопонимания и путаницы. Я написал статью, которая, надеюсь, наведет порядок в этом хаосе: rgomes-info.blogspot.co.uk/2013/01/…
Ричард Гомес,
Если вы хотите иметь функциональные объекты для типов данных union, вам понадобится шаблон посетителей. Вы можете задаться вопросом, что представляют собой функциональные объекты и типы данных объединения, тогда стоит прочитать ccs.neu.edu/home/matthias/htdc.html
Вэй Цю
Примеры здесь и здесь .
jaco0646

Ответы:

315

Я не очень знаком с моделью посетителя. Посмотрим, правильно ли я понял. Предположим, у вас есть иерархия животных

class Animal {  };
class Dog: public Animal {  };
class Cat: public Animal {  };

(Предположим, это сложная иерархия с устоявшимся интерфейсом.)

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

class Animal
{ public: virtual void makeSound() = 0; };

class Dog : public Animal
{ public: void makeSound(); };

void Dog::makeSound()
{ std::cout << "woof!\n"; }

class Cat : public Animal
{ public: void makeSound(); };

void Cat::makeSound()
{ std::cout << "meow!\n"; }

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

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

class Operation
{
public:
    virtual void hereIsADog(Dog *d) = 0;
    virtual void hereIsACat(Cat *c) = 0;
};

Затем мы модифицируем иерархию для принятия новых операций:

class Animal
{ public: virtual void letsDo(Operation *v) = 0; };

class Dog : public Animal
{ public: void letsDo(Operation *v); };

void Dog::letsDo(Operation *v)
{ v->hereIsADog(this); }

class Cat : public Animal
{ public: void letsDo(Operation *v); };

void Cat::letsDo(Operation *v)
{ v->hereIsACat(this); }

Наконец, мы реализуем фактическую операцию, не изменяя ни Cat, ни Dog :

class Sound : public Operation
{
public:
    void hereIsADog(Dog *d);
    void hereIsACat(Cat *c);
};

void Sound::hereIsADog(Dog *d)
{ std::cout << "woof!\n"; }

void Sound::hereIsACat(Cat *c)
{ std::cout << "meow!\n"; }

Теперь у вас есть возможность добавлять операции без изменения иерархии. Вот как это работает:

int main()
{
    Cat c;
    Sound theSound;
    c.letsDo(&theSound);
}
Федерико А. Рампони
источник
19
С.Лотт, прогулка по дереву - это на самом деле не образец для посетителей. (Это «иерархический шаблон посетителей», который до смешного совершенно отличается.) Невозможно показать шаблон GoF Visitor без использования наследования или реализации интерфейса.
Великолепно
14
@Knownasilya - Это не правда. Оператор & дает адрес Sound-Object, который необходим интерфейсу. letsDo(Operation *v) нужен указатель
AquilaRapax
3
просто для ясности, правильный ли этот пример шаблона дизайна посетителя?
Годзилла
4
После долгих раздумий, я удивляюсь, почему вы вызвали два метода здесь: Isadog и hereIsACat, хотя вы уже передаете методы «Собака и Кошка». Я бы предпочел простой executeTask (Object * obj), и вы приведете этот объект в класс Operation. (и на языке, поддерживающем переопределение, нет необходимости в кастинге)
Абдалрахман Шато
6
В вашем «основном» примере в конце: theSound.hereIsACat(c)сделал бы работу, как вы оправдываете все накладные расходы, вносимые шаблоном? двойная диспетчеризация является оправданием.
Франсу
131

Причиной вашего замешательства, вероятно, является то, что Посетитель - смертельный неправильный человек. Многие (выдающиеся 1 !) Программисты наткнулись на эту проблему. Что он на самом деле делает, так это реализует двойную диспетчеризацию в языках, которые не поддерживают его изначально (большинство из них этого не делают).


1) Мой любимый пример - Скотт Мейерс, известный автор «Эффективного C ++», который назвал это одним из своих самых важных C ++, ага! моменты когда-либо .

Конрад Рудольф
источник
3
+1 «нет шаблона» - идеальный ответ. наиболее одобренный ответ доказывает, что многим программистам на с ++ еще предстоит осознать ограничения виртуальных функций по сравнению с «adhoc» полиморфизмом, используя перечисление типов и регистр переключателей (путь c). Это может быть более аккуратным и невидимым для использования виртуальным, но это все еще ограничено одной отправкой. По моему личному мнению, это самый большой недостаток с ++.
user3125280
@ user3125280 Я уже прочитал 4/5 статей и главу «Шаблоны проектирования» о шаблоне «Посетитель», и ни одна из них не объясняет преимущества использования этого неясного шаблона по сравнению со случаем stmt или когда вы можете использовать одну из них над другой. Спасибо, по крайней мере, за это!
spinkus
4
@ Сэм , я уверен , что они объясняют это - это то же самое преимущество , что вы всегда получаете от подклассов / выполнения полиморфизма через switch: switchжесткие коды принятия решений на стороне клиента (код дублирование) и не обеспечивает статическую проверку типов ( проверка на полноту и отчетливость дел и т. д.). Шаблон посетителя проверяется средством проверки типов и обычно упрощает код клиента.
Конрад Рудольф
@KonradRudolph спасибо за это. Однако, отметим, что он не рассматривается явно в шаблонах или в статье в Википедии, например. Я не согласен с вами, но вы могли бы поспорить, что использование case stmt также имеет преимущества, поэтому странно, что он обычно не контрастирует: 1. вам не нужен метод accept () для объектов вашей коллекции. 2. Посетитель может обрабатывать объекты неизвестного типа. Таким образом, случай stmt лучше подходит для работы с объектными структурами с изменяемым набором задействованных типов. Patterns действительно допускает, что шаблон Visitor не очень подходит для такого сценария (p333).
spinkus
1
@SamPinkus Конрад на месте - именно поэтому virtualподобные функции так полезны в современных языках программирования - они являются основным строительным блоком расширяемых программ - на мой взгляд, способ c (вложенный переключатель или сопоставление с шаблоном и т. Д. В зависимости от выбранного языка) Гораздо чище в коде, который не должен быть расширяемым, и я был приятно удивлен, увидев этот стиль в таком сложном программном обеспечении, как Prover 9. Более важно, что любой язык, который хочет обеспечить расширяемость, вероятно, должен приспосабливаться к лучшим шаблонам диспетчеризации, чем рекурсивная одиночная диспетчеризация (т.е. посетитель).
user3125280
84

Все здесь правы, но я думаю, что это не касается "когда". Во-первых, из Design Patterns:

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

Теперь давайте подумаем о простой иерархии классов. У меня есть классы 1, 2, 3 и 4 и методы A, B, C и D. Выложите их, как в электронной таблице: классы - это строки, а методы - столбцы.

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

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

Кстати, именно эту проблему намеревается решить шаблон Scala.

Даниэль С. Собрал
источник
Зачем мне использовать шаблон посетителя над классом utlity. я могу назвать свой служебный класс следующим образом: AnalyticsManger.visit (someObjectToVisit) против AnalyticsVisitor.visit (someOjbectToVisit). Какая разница ? они оба делят беспокойство, верно? надеюсь, ты сможешь помочь.
j2emanue
@ j2emanue Поскольку шаблон посетителя использует корректную перегрузку посетителя во время выполнения. В то время как ваш код нуждается в приведении типов для вызова корректной перегрузки.
Доступ запрещен
есть ли повышение эффективности с этим? я предполагаю, что это избегает бросать свою хорошую идею
j2emanue
@ j2emanue идея в том, чтобы написать код, который соответствует принципу открытого / закрытого, а не производительности. Смотри открыто закрыто у дяди Боба butunclebob.com/ArticleS.UncleBob.PrinciplesOfOod
доступ запрещен
22

Посетители шаблон дизайна работает очень хорошо для «рекурсивных» структур , таких как дерева каталогов, XML - структуры, или очертания документов.

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

Вот типичная рекурсивная структура узлов. Может быть каталогом или тегом XML. [Если вы Java-человек, представьте себе множество дополнительных методов для создания и поддержки списка детей.]

class TreeNode( object ):
    def __init__( self, name, *children ):
        self.name= name
        self.children= children
    def visit( self, someVisitor ):
        someVisitor.arrivedAt( self )
        someVisitor.down()
        for c in self.children:
            c.visit( someVisitor )
        someVisitor.up()

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

Вот суперкласс для посетителей. Используется visitметодом. Он «достигает» каждого узла в структуре. Поскольку visitметод вызывает upи down, посетитель может отслеживать глубину.

class Visitor( object ):
    def __init__( self ):
        self.depth= 0
    def down( self ):
        self.depth += 1
    def up( self ):
        self.depth -= 1
    def arrivedAt( self, aTreeNode ):
        print self.depth, aTreeNode.name

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

Вот приложение. Он строит древовидную структуру someTree. Это создает Visitor, dumpNodes.

Затем он применяет dumpNodesк дереву. dumpNodeОбъект будет «посетить» каждый узел в дереве.

someTree= TreeNode( "Top", TreeNode("c1"), TreeNode("c2"), TreeNode("c3") )
dumpNodes= Visitor()
someTree.visit( dumpNodes )

visitАлгоритм TreeNode гарантирует, что каждый TreeNode используется в качестве аргумента для arrivedAtметода посетителя .

С. Лотт
источник
8
Как утверждают другие, это «иерархическая структура посетителей».
PPC-кодер
1
@ PPC-Coder Какая разница между «иерархическим шаблоном посетителя» и шаблоном посетителя?
Тим Ловелл-Смит
3
Иерархический шаблон посетителей является более гибким, чем классический шаблон посетителей. Например, с помощью иерархического шаблона вы можете отслеживать глубину обхода и решать, какую ветвь обходить или прекратить обходить все вместе. Классический посетитель не имеет этой концепции и посетит все узлы.
PPC-кодер
18

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

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

Классический пример для компиляторов и тому подобное. Абстрактное синтаксическое дерево (AST) может точно определить структуру языка программирования, но операции, которые вы можете выполнять в AST, будут меняться по мере продвижения вашего проекта: генераторы кода, симпатичные принтеры, отладчики, анализ показателей сложности.

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

(Я слышал, что он утверждал, что шаблон Visitor вступает в противоречие с хорошими практиками ОО, поскольку он перемещает операции данных от данных. Шаблон посетителя полезен именно в ситуации, когда обычные практики ОО терпят неудачу.)

Oddthinking
источник
Мне также хотелось бы узнать ваше мнение о следующем: Почему я должен использовать шаблон посетителя только для класса utlity. я могу назвать свой служебный класс следующим образом: AnalyticsManger.visit (someObjectToVisit) против AnalyticsVisitor.visit (someOjbectToVisit). Какая разница ? они оба делят беспокойство, верно? надеюсь, ты сможешь помочь.
j2emanue
@ j2emanue: я не понимаю вопроса. Я предлагаю вам уточнить это и опубликовать как полный вопрос для любого, чтобы ответить.
Нечетное
1
я разместил новый вопрос здесь: stackoverflow.com/questions/52068876/…
j2emanue
14

Существует по крайней мере три веские причины для использования шаблона посетителя:

  1. Уменьшите распространение кода, который лишь немного отличается при изменении структуры данных.

  2. Примените одно и то же вычисление к нескольким структурам данных, не изменяя код, который реализует вычисление.

  3. Добавить информацию в устаревшие библиотеки без изменения устаревшего кода.

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

Ричард Гомес
источник
1
Я прокомментировал вашу статью с самым большим использованием, которое я видел для посетителя. Мысли?
Джордж Мауэр
13

Как уже отмечал Конрад Рудольф, он подходит для случаев, когда нам нужна двойная отправка

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

Пример :

Допустим, у меня есть 3 типа мобильных устройств - iPhone, Android, Windows Mobile.

На всех этих трех устройствах установлено радио Bluetooth.

Предположим, что радио «синий зуб» может быть от двух отдельных OEM-производителей - Intel и Broadcom.

Просто для того, чтобы сделать пример релевантным для нашего обсуждения, давайте также предположим, что API-интерфейсы, предоставляемые радио Intel, отличаются от тех, которые представлены радио Broadcom.

Вот так выглядят мои занятия -

введите описание изображения здесь введите описание изображения здесь

Теперь я хотел бы ввести операцию - Включение Bluetooth на мобильном устройстве.

Его функция подписи должна выглядеть примерно так:

 void SwitchOnBlueTooth(IMobileDevice mobileDevice, IBlueToothRadio blueToothRadio)

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

В принципе, это становится матрицей 3 x 2, где я пытаюсь векторизовать правильную операцию в зависимости от правильного типа вовлеченных объектов.

Полиморфное поведение в зависимости от типа обоих аргументов.

введите описание изображения здесь

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

Двойная диспетчеризация здесь необходима благодаря матрице 3х2

Вот как будет выглядеть установка - введите описание изображения здесь

Я написал пример, чтобы ответить на другой вопрос, код и его объяснение упоминаются здесь .

Капур
источник
9

Я нашел это проще в следующих ссылках:

В http://www.remondo.net/visitor-pattern-example-csharp/ я нашел пример, который показывает фиктивный пример, который показывает, что является преимуществом шаблона посетителя. Здесь у вас есть различные классы контейнеров для Pill:

namespace DesignPatterns
{
    public class BlisterPack
    {
        // Pairs so x2
        public int TabletPairs { get; set; }
    }

    public class Bottle
    {
        // Unsigned
        public uint Items { get; set; }
    }

    public class Jar
    {
        // Signed
        public int Pieces { get; set; }
    }
}

Как вы видите выше, у вас BilsterPackесть пары таблеток, поэтому вам нужно умножить количество пар на 2. Также вы можете заметить, что Bottleиспользуется unitдругой тип данных, и его нужно разыграть.

Таким образом, в основном методе вы можете рассчитать количество таблеток, используя следующий код:

foreach (var item in packageList)
{
    if (item.GetType() == typeof (BlisterPack))
    {
        pillCount += ((BlisterPack) item).TabletPairs * 2;
    }
    else if (item.GetType() == typeof (Bottle))
    {
        pillCount += (int) ((Bottle) item).Items;
    }
    else if (item.GetType() == typeof (Jar))
    {
        pillCount += ((Jar) item).Pieces;
    }
}

Обратите внимание, что приведенный выше код нарушает Single Responsibility Principle. Это означает, что вы должны изменить основной код метода, если добавляете новый тип контейнера. Также делать переключение дольше - плохая практика.

Итак, введя следующий код:

public class PillCountVisitor : IVisitor
{
    public int Count { get; private set; }

    #region IVisitor Members

    public void Visit(BlisterPack blisterPack)
    {
        Count += blisterPack.TabletPairs * 2;
    }

    public void Visit(Bottle bottle)
    {
        Count += (int)bottle.Items;
    }

    public void Visit(Jar jar)
    {
        Count += jar.Pieces;
    }

    #endregion
}

Вы переместили ответственность за подсчет числа Pills на вызываемый класс PillCountVisitor(и мы удалили оператор switch case). Это означает, что всякий раз, когда вам нужно добавить новый тип контейнера для таблеток, вы должны изменить только PillCountVisitorкласс. Также IVisitorинтерфейс уведомления является общим для использования в других сценариях.

Добавив метод Accept в класс контейнера для таблеток:

public class BlisterPack : IAcceptor
{
    public int TabletPairs { get; set; }

    #region IAcceptor Members

    public void Accept(IVisitor visitor)
    {
        visitor.Visit(this);
    }

    #endregion
}

мы разрешаем посетителю посещать классы контейнеров для таблеток.

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

var visitor = new PillCountVisitor();

foreach (IAcceptor item in packageList)
{
    item.Accept(visitor);
}

Это означает, что: каждый контейнер с таблетками позволяет PillCountVisitorпосетителю увидеть количество таблеток. Он умеет считать твои таблетки.

На это visitor.Countимеет значение таблетки.

В http://butunclebob.com/ArticleS.UncleBob.IuseVisitor вы видите реальный сценарий, в котором вы не можете использовать полиморфизм (ответ), чтобы следовать принципу единой ответственности. На самом деле в:

public class HourlyEmployee extends Employee {
  public String reportQtdHoursAndPay() {
    //generate the line for this hourly employee
  }
}

этот reportQtdHoursAndPayметод предназначен для отчетности и представления, и это нарушает принцип единой ответственности. Так что лучше использовать шаблон посетителя, чтобы преодолеть проблему.

Сейед Мортеза Мусави
источник
2
Привет Сэйд, пожалуйста, отредактируйте свой ответ, чтобы добавить части, которые вы нашли наиболее поучительными. Обычно SO не одобряет ответы, содержащие только ссылки, поскольку цель состоит в том, чтобы стать базой знаний, а ссылки не работают.
Джордж Мауэр
8

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

Вот причины для использования шаблона:

1) Мы хотим определять новые операции, не меняя модель каждый раз, потому что модель не меняется часто, а операции часто меняются.

2) Мы не хотим связывать модель и поведение, потому что мы хотим иметь многократно используемую модель в нескольких приложениях или мы хотим иметь расширяемую модель, которая позволяет клиентским классам определять свое поведение со своими собственными классами.

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

4) Мы используем проектирование модели предметной области, и классы моделей той же иерархии выполняют слишком много разных вещей, которые можно собрать где-то еще .

5) Нам нужна двойная отправка .
У нас есть переменные, объявленные с типами интерфейса, и мы хотим иметь возможность обрабатывать их в соответствии с их типом среды выполнения… конечно, без использования if (myObj instanceof Foo) {}или каких-либо хитростей.
Идея состоит, например, в том, чтобы передать эти переменные в методы, которые объявляют конкретный тип интерфейса в качестве параметра для применения определенной обработки. Этот способ не возможен из коробки, так как языки основаны на единой диспетчеризации, потому что выбранный, вызванный во время выполнения, зависит только от типа получателя во время выполнения.
Обратите внимание, что в Java вызываемый метод (подпись) выбирается во время компиляции и зависит от объявленного типа параметров, а не от их типа во время выполнения.

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

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


Пример на Java

Я проиллюстрирую добавленную стоимость паттерна на примере шахмат, где мы хотели бы определить обработку, когда игрок запрашивает перемещение фигуры.

Без использования шаблона посетителя мы могли бы определить поведение перемещения элементов непосредственно в подклассах элементов.
Мы могли бы иметь, например, такой Pieceинтерфейс:

public interface Piece{

    boolean checkMoveValidity(Coordinates coord);

    void performMove(Coordinates coord);

    Piece computeIfKingCheck();

}

Каждый подкласс Piece будет реализовывать это, например:

public class Pawn implements Piece{

    @Override
    public boolean checkMoveValidity(Coordinates coord) {
        ...
    }

    @Override
    public void performMove(Coordinates coord) {
        ...
    }

    @Override
    public Piece computeIfKingCheck() {
        ...
    }

}

И то же самое для всех подклассов Piece.
Вот класс диаграммы, который иллюстрирует этот дизайн:

[диаграмма класса модели

Этот подход имеет три важных недостатка:

- поведение, такое как performMove()или computeIfKingCheck()очень вероятно, будет использовать общую логику.
Например, какой бы бетон ни был Piece, performMove()он, наконец, установит текущую фигуру в определенном месте и потенциально заберет фигуру противника.
Разделение связанных поведений на несколько классов вместо того, чтобы собирать их, каким-то образом побеждает единую схему ответственности. Делать их ремонтопригоднее сложнее.

- обработка checkMoveValidity()не должна быть чем-то, что Pieceподклассы могут видеть или изменять.
Это проверка, которая выходит за рамки действий человека или компьютера. Эта проверка выполняется при каждом действии, запрошенном игроком, чтобы убедиться, что запрошенный ход фигуры действителен.
Поэтому мы даже не хотим предоставлять это в Pieceинтерфейсе.

- В сложных шахматных играх для разработчиков ботов, как правило, приложение предоставляет стандартный API ( Pieceинтерфейсы, подклассы, Board, общее поведение и т. Д.) И позволяет разработчикам обогащать свою стратегию ботов.
Чтобы сделать это, мы должны предложить модель, в которой данные и поведение не связаны в Pieceреализациях.

Итак, давайте использовать шаблон посетителя!

У нас есть два вида структуры:

- модельные классы, которые принимают к посещению (штуки)

- посетители, которые их посещают (двигательные операции)

Вот диаграмма классов, которая иллюстрирует шаблон:

введите описание изображения здесь

В верхней части у нас есть посетители, а в нижней части - классы моделей.

Вот PieceMovingVisitorинтерфейс (поведение, указанное для каждого вида Piece):

public interface PieceMovingVisitor {

    void visitPawn(Pawn pawn);

    void visitKing(King king);

    void visitQueen(Queen queen);

    void visitKnight(Knight knight);

    void visitRook(Rook rook);

    void visitBishop(Bishop bishop);

}

Часть определена сейчас:

public interface Piece {

    void accept(PieceMovingVisitor pieceVisitor);

    Coordinates getCoordinates();

    void setCoordinates(Coordinates coordinates);

}

Его ключевой метод:

void accept(PieceMovingVisitor pieceVisitor);

Это обеспечивает первую отправку: вызов, основанный на Pieceполучателе.
Во время компиляции метод привязан к accept()методу интерфейса Piece, а во время выполнения ограниченный метод будет вызван в Pieceклассе времени выполнения .
И именно accept()реализация метода будет выполнять вторую диспетчеризацию.

Действительно, каждый Pieceподкласс, который хочет посетить PieceMovingVisitorобъект, вызывает PieceMovingVisitor.visit()метод, передавая сам аргумент.
Таким образом, компилятор ограничивает, как только время компиляции, тип объявленного параметра конкретным типом.
Есть вторая отправка.
Вот Bishopподкласс, который иллюстрирует это:

public class Bishop implements Piece {

    private Coordinates coord;

    public Bishop(Coordinates coord) {
        super(coord);
    }

    @Override
    public void accept(PieceMovingVisitor pieceVisitor) {
        pieceVisitor.visitBishop(this);
    }

    @Override
    public Coordinates getCoordinates() {
        return coordinates;
    }

   @Override
    public void setCoordinates(Coordinates coordinates) {
        this.coordinates = coordinates;
   }

}

А вот пример использования:

// 1. Player requests a move for a specific piece
Piece piece = selectPiece();
Coordinates coord = selectCoordinates();

// 2. We check with MoveCheckingVisitor that the request is valid
final MoveCheckingVisitor moveCheckingVisitor = new MoveCheckingVisitor(coord);
piece.accept(moveCheckingVisitor);

// 3. If the move is valid, MovePerformingVisitor performs the move
if (moveCheckingVisitor.isValid()) {
    piece.accept(new MovePerformingVisitor(coord));
}

Недостатки посетителя

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

1) Риск уменьшить / сломать инкапсуляцию

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

Например, так как MovePerformingVisitor класс должен установить координаты фактической части, Pieceинтерфейс должен предоставить способ сделать это:

void setCoordinates(Coordinates coordinates);

Ответственность за Pieceизменение координат теперь открыта для других классов, кроме Pieceподклассов.
Перемещение обработки, выполняемой посетителем в Pieceподклассы, также не вариант.
Это действительно создаст другую проблему, так как Piece.accept()принимает любую реализацию посетителя. Он не знает, что выполняет посетитель, и поэтому не знает, нужно ли и как изменить состояние фигуры.
Способ идентифицировать посетителя - выполнить постобработку в Piece.accept()соответствии с реализацией посетителя. Было бы очень плохая идея , поскольку это позволит создать высокое сцепление между реализациями Публичные и штучных подклассов и , кроме того, вероятно , потребуется использовать трюк getClass(), instanceofили любой маркер , идентифицирующий реализацию посетителей.

2) Требование изменить модель

В отличие от некоторых других шаблонов поведения, Decoratorнапример, шаблон посетителей является навязчивым.
Нам действительно нужно изменить начальный класс получателя, чтобы обеспечить accept()метод для принятия к посещению.
У нас не было проблем с Pieceподклассами, так как это наши классы .
Во встроенных или сторонних классах все не так просто.
Нам нужно обернуть или унаследовать (если мы можем) их, чтобы добавить accept()метод.

3) Направления

Шаблон создает множественные косвенные указания.
Двойная отправка означает два вызова вместо одного:

call the visited (piece) -> that calls the visitor (pieceMovingVisitor)

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

call the visited (piece) -> that calls the visitor (pieceMovingVisitor) -> that calls the visited (piece)
davidxxx
источник
6

Кей Хорстманн имеет прекрасный пример того, где можно применить Visitor в своей книге по дизайну и шаблонам . Он суммирует проблему:

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

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

Диаграмма классов FileSystem

Вот некоторые операции (функциональные возможности), которые мы могли бы реализовать с помощью этой структуры:

  • Отобразить имена элементов узла (список файлов)
  • Вывести рассчитанный размер элементов узла (где размер каталога включает в себя размер всех его дочерних элементов)
  • и т.п.

Вы можете добавить функции к каждому классу в FileSystem для реализации операций (и люди делали это в прошлом, так как совершенно очевидно, как это сделать). Проблема в том, что всякий раз, когда вы добавляете новую функциональность (строка «и т. Д.» Выше), вам может понадобиться добавлять все больше и больше методов в классы структуры. В какой-то момент, после некоторого количества операций, которые вы добавили в свое программное обеспечение, методы в этих классах больше не имеют смысла с точки зрения функциональной сплоченности классов. Например, у вас есть FileNodeметод calculateFileColorForFunctionABC(), который реализует новейшие функции визуализации в файловой системе.

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

Посетитель позволяет нам отделить функциональные возможности структуры данных (например, FileSystemNodes) от самих структур данных. Шаблон позволяет дизайну соблюдать согласованность - классы структуры данных проще (у них меньше методов), а также функциональные возможности заключены в Visitorреализации. Это делается с помощью двойной диспетчеризации (которая является сложной частью шаблона): использование accept()методов в классах структуры и visitX()методов в классах Visitor (функциональности):

Диаграмма классов FileSystem с применением Visitor

Эта структура позволяет нам добавлять новые функциональные возможности, которые работают над структурой как конкретные посетители (без изменения классов структуры).

Диаграмма классов FileSystem с применением Visitor

Например, a, PrintNameVisitorкоторый реализует функциональность листинга каталога, и a, PrintSizeVisitorкоторый реализует версию с размером. Мы могли бы вообразить, что однажды у нас будет «ExportXMLVisitor», который генерирует данные в XML, или другой посетитель, который генерирует их в JSON, и т. Д. У нас может даже быть посетитель, который отображает мое дерево каталогов с использованием графического языка, такого как DOT , для визуализации. с другой программой.

В заключение: сложность Visitor с его двойной диспетчеризацией означает, что его сложнее понять, кодировать и отлаживать. Короче говоря, он имеет высокий гик-фактор и повторяет принцип KISS. В опросе, проведенном исследователями, было показано, что Visitor является противоречивой моделью (не было единого мнения о его полезности). Некоторые эксперименты даже показали, что он не облегчает поддержку кода.

Fuhrmanator
источник
Структура каталогов, я думаю, является хорошей составной схемой, но согласна с вашим последним абзацем.
Зар
5

На мой взгляд, объем работ по добавлению новой операции более или менее одинаков с использованием Visitor Patternили прямым изменением структуры каждого элемента. Кроме того, если я добавлю новый класс элементов, скажем Cow, это повлияет на интерфейс Operation, и это распространится на все существующие классы элементов, поэтому потребуется перекомпиляция всех классов элементов. Так в чем же смысл?

kaosad
источник
4
Почти каждый раз, когда я использовал Visitor, вы работаете с обходом иерархии объектов. Рассмотрим вложенное дерево меню. Вы хотите свернуть все узлы. Если вы не реализуете посетителя, вы должны написать код обхода графа. Или с посетителем: rootElement.visit (node) -> node.collapse(). С посетителем каждый узел реализует обход графа для всех своих потомков, так что все готово.
Джордж Мауэр
@ GeorgeMauer, концепция двойной диспетчеризации прояснила для меня мотивацию: либо типозависимая логика связана с типом, либо с миром боли. Идея распределения логики обхода все еще заставляет меня задуматься. Это более эффективно? Это более ремонтопригодно? Что, если «сложить до уровня N» добавлено как требование?
ник.шорников
Эффективность @ nik.shornikov здесь не должна быть проблемой. Практически на любом языке несколько вызовов функций незначительны. Все, кроме этого, является микрооптимизацией. Это более ремонтопригодно? Смотря как. Я думаю, что в большинстве случаев это так, а иногда нет. Что касается "сгиба до уровня N". Легко передать в levelsRemainingсчетчик в качестве параметра. Уменьшите его, прежде чем вызывать детей следующего уровня. Внутри вашего посетителя if(levelsRemaining == 0) return.
Джордж Мауэр
1
@ GeorgeMauer, полностью согласен с тем, что эффективность является второстепенным вопросом. Но ремонтопригодность, например, переопределение принятой подписи, это как раз то, к чему, я думаю, должно относиться решение.
ник.шорников
5

Шаблон посетителя как та же подземная реализация для программирования Аспектного Объекта.

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

mixturez
источник
за упоминание Аспектного Объектного Программирования
milesma
5

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

Когда вы могли бы рассмотреть возможность его использования

  1. Когда у вас есть семейство классов, вы знаете, что вам придется добавить много новых действий, но по какой-то причине вы не сможете изменить или перекомпилировать семейство классов в будущем.
  2. Если вы хотите добавить новое действие и полностью определить это новое действие в одном классе посетителя, а не распределять его по нескольким классам.
  3. Когда ваш босс говорит, что вы должны создать ряд классов, которые должны что-то сделать прямо сейчас ! ... но никто на самом деле точно не знает, что это за нечто.
Эндрю Пэйт
источник
4

Я не понимал эту модель, пока не наткнулся на статью дяди Боба и не прочитал комментарии. Рассмотрим следующий код:

public class Employee
{
}

public class SalariedEmployee : Employee
{
}

public class HourlyEmployee : Employee
{
}

public class QtdHoursAndPayReport
{
    public void PrintReport()
    {
        var employees = new List<Employee>
        {
            new SalariedEmployee(),
            new HourlyEmployee()
        };
        foreach (Employee e in employees)
        {
            if (e is HourlyEmployee he)
                PrintReportLine(he);
            if (e is SalariedEmployee se)
                PrintReportLine(se);
        }
    }

    public void PrintReportLine(HourlyEmployee he)
    {
        System.Diagnostics.Debug.WriteLine("hours");
    }
    public void PrintReportLine(SalariedEmployee se)
    {
        System.Diagnostics.Debug.WriteLine("fix");
    }
}

class Program
{
    static void Main(string[] args)
    {
        new QtdHoursAndPayReport().PrintReport();
    }
}

Хотя это может выглядеть хорошо, так как подтверждает единственную ответственность, оно нарушает Open / Closed принцип . Каждый раз, когда у вас есть новый тип сотрудника, вы должны будете добавить, если с проверкой типа. И если вы этого не сделаете, вы никогда не узнаете об этом во время компиляции.

С помощью шаблона посетителя вы можете сделать свой код чище, так как он не нарушает принцип открытия / закрытия и не нарушает Единую ответственность. И если вы забудете реализовать визит, он не скомпилируется:

public abstract class Employee
{
    public abstract void Accept(EmployeeVisitor v);
}

public class SalariedEmployee : Employee
{
    public override void Accept(EmployeeVisitor v)
    {
        v.Visit(this);
    }
}

public class HourlyEmployee:Employee
{
    public override void Accept(EmployeeVisitor v)
    {
        v.Visit(this);
    }
}

public interface EmployeeVisitor
{
    void Visit(HourlyEmployee he);
    void Visit(SalariedEmployee se);
}

public class QtdHoursAndPayReport : EmployeeVisitor
{
    public void Visit(HourlyEmployee he)
    {
        System.Diagnostics.Debug.WriteLine("hourly");
        // generate the line of the report.
    }
    public void Visit(SalariedEmployee se)
    {
        System.Diagnostics.Debug.WriteLine("fix");
    } // do nothing

    public void PrintReport()
    {
        var employees = new List<Employee>
        {
            new SalariedEmployee(),
            new HourlyEmployee()
        };
        QtdHoursAndPayReport v = new QtdHoursAndPayReport();
        foreach (var emp in employees)
        {
            emp.Accept(v);
        }
    }
}

class Program
{

    public static void Main(string[] args)
    {
        new QtdHoursAndPayReport().PrintReport();
    }       
}  
}

Волшебство в том, что, хотя v.Visit(this)выглядит одинаково, на самом деле оно отличается, поскольку вызывает разные перегрузки посетителя.

В доступе отказано
источник
Да, я особенно нахожу это полезным при работе с древовидными структурами, а не только с плоскими списками (плоские списки были бы частным случаем дерева). Как вы заметили, это не очень грязно только в списках, но посетитель может быть спасителем, так как навигация между узлами становится более сложной
Джордж Мауэр
3

Основано на превосходном ответе @Federico A. Ramponi.

Просто представьте, что у вас есть эта иерархия:

public interface IAnimal
{
    void DoSound();
}

public class Dog : IAnimal
{
    public void DoSound()
    {
        Console.WriteLine("Woof");
    }
}

public class Cat : IAnimal
{
    public void DoSound(IOperation o)
    {
        Console.WriteLine("Meaw");
    }
}

Что произойдет, если вам нужно добавить метод «Walk» здесь? Это будет болезненно для всего дизайна.

В то же время добавление метода «Прогулка» порождает новые вопросы. А как насчет "Ешь" или "Спи"? Должны ли мы действительно добавлять новый метод в иерархию Animal для каждого нового действия или операции, которые мы хотим добавить? Это ужасно и очень важно, мы никогда не сможем закрыть интерфейс Animal. Таким образом, с помощью шаблона посетителя мы можем добавить новый метод в иерархию без изменения иерархии!

Итак, просто проверьте и запустите этот пример C #:

using System;
using System.Collections.Generic;

namespace VisitorPattern
{
    class Program
    {
        static void Main(string[] args)
        {
            var animals = new List<IAnimal>
            {
                new Cat(), new Cat(), new Dog(), new Cat(), 
                new Dog(), new Dog(), new Cat(), new Dog()
            };

            foreach (var animal in animals)
            {
                animal.DoOperation(new Walk());
                animal.DoOperation(new Sound());
            }

            Console.ReadLine();
        }
    }

    public interface IOperation
    {
        void PerformOperation(Dog dog);
        void PerformOperation(Cat cat);
    }

    public class Walk : IOperation
    {
        public void PerformOperation(Dog dog)
        {
            Console.WriteLine("Dog walking");
        }

        public void PerformOperation(Cat cat)
        {
            Console.WriteLine("Cat Walking");
        }
    }

    public class Sound : IOperation
    {
        public void PerformOperation(Dog dog)
        {
            Console.WriteLine("Woof");
        }

        public void PerformOperation(Cat cat)
        {
            Console.WriteLine("Meaw");
        }
    }

    public interface IAnimal
    {
        void DoOperation(IOperation o);
    }

    public class Dog : IAnimal
    {
        public void DoOperation(IOperation o)
        {
            o.PerformOperation(this);
        }
    }

    public class Cat : IAnimal
    {
        public void DoOperation(IOperation o)
        {
            o.PerformOperation(this);
        }
    }
}
Томас Эскамес
источник
ходить, есть не подходящие примеры , так как они общие для обоих Dog, а также Cat. Вы могли бы сделать их в базовом классе, чтобы они были унаследованы, или выбрать подходящий пример.
Абхинав Гауниал,
Звуки отличаются от этого, хороший пример, но не уверен, имеет ли это какое-либо отношение к шаблону посетителя
DAG
3

посетитель

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

Структура посетителей:

введите описание изображения здесь

Используйте шаблон Visitor, если:

  1. Подобные операции должны выполняться над объектами разных типов, сгруппированными в структуре.
  2. Вам необходимо выполнить много разных и не связанных операций. Он отделяет операцию от объектов
  3. Новые операции должны быть добавлены без изменения в структуре объекта
  4. Соберите связанные операции в один класс, а не заставляйте вас менять или выводить классы
  5. Добавьте функции в библиотеки классов, для которых у вас либо нет источника, либо вы не можете изменить источник

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

Если был добавлен новый объект Visitable, он требует изменения кода в классах Visitor & ConcreteVisitor . Для решения этой проблемы существует обходной путь: используйте рефлексию, которая повлияет на производительность.

Фрагмент кода:

import java.util.HashMap;

interface Visitable{
    void accept(Visitor visitor);
}

interface Visitor{
    void logGameStatistics(Chess chess);
    void logGameStatistics(Checkers checkers);
    void logGameStatistics(Ludo ludo);    
}
class GameVisitor implements Visitor{
    public void logGameStatistics(Chess chess){
        System.out.println("Logging Chess statistics: Game Completion duration, number of moves etc..");    
    }
    public void logGameStatistics(Checkers checkers){
        System.out.println("Logging Checkers statistics: Game Completion duration, remaining coins of loser");    
    }
    public void logGameStatistics(Ludo ludo){
        System.out.println("Logging Ludo statistics: Game Completion duration, remaining coins of loser");    
    }
}

abstract class Game{
    // Add game related attributes and methods here
    public Game(){

    }
    public void getNextMove(){};
    public void makeNextMove(){}
    public abstract String getName();
}
class Chess extends Game implements Visitable{
    public String getName(){
        return Chess.class.getName();
    }
    public void accept(Visitor visitor){
        visitor.logGameStatistics(this);
    }
}
class Checkers extends Game implements Visitable{
    public String getName(){
        return Checkers.class.getName();
    }
    public void accept(Visitor visitor){
        visitor.logGameStatistics(this);
    }
}
class Ludo extends Game implements Visitable{
    public String getName(){
        return Ludo.class.getName();
    }
    public void accept(Visitor visitor){
        visitor.logGameStatistics(this);
    }
}

public class VisitorPattern{
    public static void main(String args[]){
        Visitor visitor = new GameVisitor();
        Visitable games[] = { new Chess(),new Checkers(), new Ludo()};
        for (Visitable v : games){
            v.accept(visitor);
        }
    }
}

Объяснение:

  1. Visitable( Element) является интерфейсом, и этот метод интерфейса должен быть добавлен к набору классов.
  2. Visitorинтерфейс, который содержит методы для выполнения операций над Visitableэлементами
  3. GameVisitorэто класс, который реализует Visitorинтерфейс ( ConcreteVisitor).
  4. Каждый Visitableэлемент принимает Visitorи вызывает соответствующий метод Visitorинтерфейса.
  5. Можно относиться Gameкак к Elementконкретным играм, так и к Chess,Checkers and Ludoкак ConcreteElements.

В приведенном выше примере Chess, Checkers and Ludoпредставлены три разные игры (и Visitableклассы). В один прекрасный день я столкнулся со сценарием для записи статистики каждой игры. Таким образом, не изменяя отдельный класс для реализации функциональности статистики, вы можете централизовать эту ответственность в GameVisitorклассе, что поможет вам без изменения структуры каждой игры.

вывод:

Logging Chess statistics: Game Completion duration, number of moves etc..
Logging Checkers statistics: Game Completion duration, remaining coins of loser
Logging Ludo statistics: Game Completion duration, remaining coins of loser

Ссылаться на

статья о дизайне

sourcemaking статья

Больше подробностей

декоратор

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

Похожие сообщения:

Декоратор Pattern для IO

Когда использовать шаблон декоратора?

Равиндра Бабу
источник
2

Мне очень нравится описание и пример из http://python-3-patterns-idioms-test.readthedocs.io/en/latest/Visitor.html .

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

Шаблон проектирования, который решает такую ​​проблему, называется «посетитель» (последний в книге «Шаблоны проектирования») и основывается на схеме двойной диспетчеризации, показанной в последнем разделе.

Шаблон посетителя позволяет расширить интерфейс основного типа, создав отдельную иерархию классов типа Visitor для виртуализации операций, выполняемых с основным типом. Объекты первичного типа просто «принимают» посетителя, а затем вызывают динамически связанную функцию-член посетителя.

wojcikstefan
источник
В то время как технически шаблон Visitor это просто базовая двойная диспетчеризация из их примера. Я бы сказал, что полезность не особенно видна из этого в одиночку.
Джордж Мауэр
1

Хотя я понял, как и когда, я никогда не понимал, почему. В случае, если это помогает кому-то с опытом в языке, подобном C ++, вы должны прочитать это очень внимательно.

Для ленивых мы используем шаблон посетителей, потому что «хотя виртуальные функции отправляются динамически в C ++, перегрузка функций выполняется статически» .

Или, другими словами, чтобы убедиться, что CollideWith (ApolloSpacecraft &) вызывается при передаче ссылки на SpaceShip, которая фактически привязана к объекту ApolloSpacecraft.

class SpaceShip {};
class ApolloSpacecraft : public SpaceShip {};
class ExplodingAsteroid : public Asteroid {
public:
  virtual void CollideWith(SpaceShip&) {
    cout << "ExplodingAsteroid hit a SpaceShip" << endl;
  }
  virtual void CollideWith(ApolloSpacecraft&) {
    cout << "ExplodingAsteroid hit an ApolloSpacecraft" << endl;
  }
}
деревенщина
источник
2
Использование динамической диспетчеризации в шаблоне посетителя меня совершенно сбивает с толку. Предлагаемое использование шаблона описывает ветвление, которое может быть сделано во время компиляции. Эти случаи, казалось бы, были бы лучше с шаблоном функции.
Праксеолит
0

Спасибо за потрясающее объяснение @Federico A. Ramponi , я только что сделал это в java- версии. Надеюсь, это может быть полезно.

Также, как указал @Konrad Rudolph , на самом деле это двойная диспетчеризация, использующая два конкретных экземпляра вместе для определения методов времени выполнения.

Так что на самом деле нет необходимости создавать общий интерфейс для исполнителя операций, если у нас правильно определен интерфейс операций .

import static java.lang.System.out;
public class Visitor_2 {
    public static void main(String...args) {
        Hearen hearen = new Hearen();
        FoodImpl food = new FoodImpl();
        hearen.showTheHobby(food);
        Katherine katherine = new Katherine();
        katherine.presentHobby(food);
    }
}

interface Hobby {
    void insert(Hearen hearen);
    void embed(Katherine katherine);
}


class Hearen {
    String name = "Hearen";
    void showTheHobby(Hobby hobby) {
        hobby.insert(this);
    }
}

class Katherine {
    String name = "Katherine";
    void presentHobby(Hobby hobby) {
        hobby.embed(this);
    }
}

class FoodImpl implements Hobby {
    public void insert(Hearen hearen) {
        out.println(hearen.name + " start to eat bread");
    }
    public void embed(Katherine katherine) {
        out.println(katherine.name + " start to eat mango");
    }
}

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

import static java.lang.System.out;
public class Visitor_2 {
    public static void main(String...args) {
        Hearen hearen = new Hearen();
        FoodImpl food = new FoodImpl();
        hearen.showHobby(food);
        Katherine katherine = new Katherine();
        katherine.showHobby(food);
    }
}

interface Hobby {
    void insert(Hearen hearen);
    void insert(Katherine katherine);
}

abstract class Person {
    String name;
    protected Person(String n) {
        this.name = n;
    }
    abstract void showHobby(Hobby hobby);
}

class Hearen extends  Person {
    public Hearen() {
        super("Hearen");
    }
    @Override
    void showHobby(Hobby hobby) {
        hobby.insert(this);
    }
}

class Katherine extends Person {
    public Katherine() {
        super("Katherine");
    }

    @Override
    void showHobby(Hobby hobby) {
        hobby.insert(this);
    }
}

class FoodImpl implements Hobby {
    public void insert(Hearen hearen) {
        out.println(hearen.name + " start to eat bread");
    }
    public void insert(Katherine katherine) {
        out.println(katherine.name + " start to eat mango");
    }
}
Hearen
источник
0

Ваш вопрос, когда знать:

я не первый код с шаблоном посетителя. я кодирую стандарт и жду, когда возникнет необходимость, а затем рефакторинг. Допустим, у вас есть несколько платежных систем, которые вы установили по одной за раз. Во время оформления заказа у вас может быть много условий if (или instanceOf), например:

//psuedo code
    if(payPal) 
    do paypal checkout 
    if(stripe)
    do strip stuff checkout
    if(payoneer)
    do payoneer checkout

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

new PaymentCheckoutVistor(paymentType).visit()

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

j2emanue
источник