Когда следует использовать шаблон дизайна посетителя?

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

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

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

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

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

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

 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"; } 

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

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

 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); } 

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


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

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

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

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

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

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

Кстати, это проблема, с которой сталкивается модель Scala.

Шаблон проектирования Visitor отлично работает для «рекурсивных» структур, таких как деревья каталогов, структуры XML или контуры документа.

Объект посетителя посещает каждый узел в рекурсивной структуре: каждый каталог, каждый тег XML, что угодно. Объект посетителя не пересекает структуру. Вместо этого методы 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 применяет объект Visitor к каждому узлу в структуре. В этом случае это нисходящий посетитель. Вы можете изменить структуру метода visit чтобы сделать снизу вверх или какой-либо другой порядок.

Вот суперclass для посетителей. Он используется методом 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 

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Пример :

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

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

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

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

Вот как выглядят мои classы –

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

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

Его подпись функции должна понравиться что-то вроде этого –

  void SwitchOnBlueTooth(IMobileDevice mobileDevice, IBlueToothRadio blueToothRadio) 

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

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

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

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

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

Двойная отправка необходима из-за матрицы 3×2

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

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

Мне было проще в следующих ссылках:

В http://www.remondo.net/visitor-pattern-example-csharp/ Я нашел пример, который показывает примерный пример, показывающий, что полезно для шаблона посетителя. Здесь у вас разные classы контейнеров для 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; } } } 

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

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

 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 } 

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

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

 public class BlisterPack : IAcceptor { public int TabletPairs { get; set; } #region IAcceptor Members public void Accept(IVisitor visitor) { visitor.Visit(this); } #endregion } 

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

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

 var visitor = new PillCountVisitor(); foreach (IAcceptor item in packageList) { item.Accept(visitor); } 

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

У visitor.Count имеет ценность таблеток.

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

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

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

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

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

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

Кей Хёрстманн – отличный пример того, как обратиться к посетителю в его книге OO Design and patterns . Он суммирует проблему:

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

На основе отличного ответа от @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"); } } 

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

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

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

 using System; using System.Collections.Generic; namespace VisitorPattern { class Program { static void Main(string[] args) { var animals = new List { 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); } } } 

посетитель

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

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

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

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

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

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

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

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

 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 ) – это интерфейс, и этот метод интерфейса должен быть добавлен к набору classов.
  2. Visitor – это интерфейс, который содержит методы для выполнения операции над элементами Visitable .
  3. GameVisitor – это class, который реализует интерфейс Visitor ( ConcreteVisitor ).
  4. Каждый элемент Visitable принимает Visitor и вызывает соответствующий метод интерфейса Visitor .
  5. Вы можете рассматривать Game as Element и конкретные игры, такие как Chess,Checkers and Ludo как ConcreteElements .

В приведенном выше примере Chess, Checkers and Ludo – это три разные игры (и classы Visitable ). В один прекрасный день я столкнулся со сценарием для регистрации статистики каждой игры. Поэтому, не изменяя индивидуальный class для реализации функций статистики, вы можете централизовать эту ответственность в classе 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 

Ссылаться на

oodesign статья

исходная статья

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

декоратор

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

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

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

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

Double dispatch is just one reason among others to use this pattern .
But note that it is the single way to implement double or more dispatch in languages that uses a single dispatch paradigm.

Here are reasons to use the pattern :

1) We want to define new operations without changing the model at each time because the model doesn’t change often wile operations change frequently.

2) We don’t want to couple model and behavior because we want to have a reusable model in multiple applications or we want to have an extensible model that allow client classes to define their behaviors with their own classes.

3) We have common operations that depend on the concrete type of the model but we don’t want to implement the logic in each subclass as that would explode common logic in multiple classes and so in multiple places .

4) We are using a domain model design and model classes of the same hierarchy perform too many distinct things that could be gathered somewhere else .

5) We need a double dispatch .
We have variables declared with interface types and we want to be able to process them according their runtime type … of course without using if (myObj instanceof Foo) {} or any trick.
The idea is for example to pass these variables to methods that declares a concrete type of the interface as parameter to apply a specific processing. This way of doing is not possible out of the box with languages relies on a single-dispatch because the chosen invoked at runtime depends only on the runtime type of the receiver.
Note that in Java, the method (signature) to call is chosen at compile time and it depends on the declared type of the parameters, not their runtime type.

The last point that is a reason to use the visitor is also a consequence because as you implement the visitor (of course for languages that doesn’t support multiple dispatch), you necessarily need to introduce a double dispatch implementation.

Note that the traversal of elements (iteration) to apply the visitor on each one is not a reason to use the pattern.
You use the pattern because you split model and processing.
And by using the pattern, you benefit in addition from an iterator ability.
This ability is very powerful and goes beyond iteration on common type with a specific method as accept() is a generic method.
It is a special use case. So I will put that to one side.


Example in Java

I will illustrate the added value of the pattern with a chess example where we would like to define processing as player requests a piece moving.

Without the visitor pattern use, we could define piece moving behaviors directly in the pieces subclasses.
We could have for example a Piece interface such as :

 public interface Piece{ boolean checkMoveValidity(Coordinates coord); void performMove(Coordinates coord); Piece computeIfKingCheck(); } 

Each Piece subclass would implement it such as :

 public class Pawn implements Piece{ @Override public boolean checkMoveValidity(Coordinates coord) { ... } @Override public void performMove(Coordinates coord) { ... } @Override public Piece computeIfKingCheck() { ... } } 

And the same thing for all Piece subclasses.
Here is a diagram class that illustrates this design :

[model class diagram

This approach presents three important drawbacks :

– behaviors such as performMove() or computeIfKingCheck() will very probably use common logic.
For example whatever the concrete Piece , performMove() will finally set the current piece to a specific location and potentially takes the opponent piece.
Splitting related behaviors in multiple classes instead of gathering them defeats in a some way the single responsibility pattern. Making their maintainability harder.

– processing as checkMoveValidity() should not be something that the Piece subclasses may see or change.
It is check that goes beyond human or computer actions. This check is performed at each action requested by a player to ensure that the requested piece move is valid.
So we even don’t want to provide that in the Piece interface.

– In chess games challenging for bot developers, generally the application provides a standard API ( Piece interfaces, subclasses, Board, common behaviors, etc…) and let developers enrich their bot strategy.
To be able to do that, we have to propose a model where data and behaviors are not tightly coupled in the Piece implementations.

So let’s go to use the visitor pattern !

We have two kinds of structure :

– the model classes that accept to be visited (the pieces)

– the visitors that visit them (moving operations)

Here is a class diagram that illustrates the pattern :

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

In the upper part we have the visitors and in the lower part we have the model classes.

Here is the PieceMovingVisitor interface (behavior specified for each kind of 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); } 

The Piece is defined now :

 public interface Piece { void accept(PieceMovingVisitor pieceVisitor); Coordinates getCoordinates(); void setCoordinates(Coordinates coordinates); } 

Its key method is :

 void accept(PieceMovingVisitor pieceVisitor); 

It provides the first dispatch : a invocation based on the Piece receiver.
At compile time, the method is bound to the accept() method of the Piece interface and at runtime, the bounded method will be invoked on the runtime Piece class.
And it is the accept() method implementation that will perform a second dispatch.

Indeed, each Piece subclass that wants to be visited by a PieceMovingVisitor object invokes the PieceMovingVisitor.visit() method by passing as argument itself.
In this way, the compiler bounds as soon as the compile time, the type of the declared parameter with the concrete type.
There is the second dispatch.
Here is the Bishop subclass that illustrates that :

 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; } } 

And here an usage example :

 // 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 drawbacks

The Visitor pattern is a very powerful pattern but it also has some important limitations that you should consider before using it.

1) Risk to reduce/break the encapsulation

In some kinds of operation, the visitor pattern may reduce or break the encapsulation of domain objects.

For example, as the MovePerformingVisitor class needs to set the coordinates of the actual piece, the Piece interface has to provide a way to do that :

 void setCoordinates(Coordinates coordinates); 

The responsibility of Piece coordinates changes is now open to other classes than Piece subclasses.
Moving the processing performed by the visitor in the Piece subclasses is not an option either.
It will indeed create another issue as the Piece.accept() accepts any visitor implementation. It doesn’t know what the visitor performs and so no idea about whether and how to change the Piece state.
A way to identify the visitor would be to perform a post processing in Piece.accept() according to the visitor implementation. It would be a very bad idea as it would create a high coupling between Visitor implementations and Piece subclasses and besides it would probably require to use trick as getClass() , instanceof or any marker identifying the Visitor implementation.

2) Requirement to change the model

Contrary to some other behavioral design patterns as Decorator for example, the visitor pattern is intrusive.
We indeed need to modify the initial receiver class to provide an accept() method to accept to be visited.
We didn’t have any issue for Piece and its subclasses as these are our classes .
In built-in or third party classes, things are not so easy.
We need to wrap or inherit (if we can) them to add the accept() method.

3) Indirections

The pattern creates multiples indirections.
The double dispatch means two invocations instead of a single one :

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

And we could have additional indirections as the visitor changes the visited object state.
It may look like a cycle :

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

While I have understood the how and when, I have never understood the why. In case it helps anyone with a background in a language like C++, you want to read this very carefully.

For the lazy, we use the visitor pattern because “while virtual functions are dispatched dynamically in C++, function overloading is done statically” .

Or, put another way, to make sure that CollideWith(ApolloSpacecraft&) is called when you pass in a SpaceShip reference that is actually bound to an ApolloSpacecraft object.

 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; } } 

I really like the description and the example from http://python-3-patterns-idioms-test.readthedocs.io/en/latest/Visitor.html .

The assumption is that you have a primary class hierarchy that is fixed; perhaps it’s from another vendor and you can’t make changes to that hierarchy. However, your intent is that you’d like to add new polymorphic methods to that hierarchy, which means that normally you’d have to add something to the base class interface. So the dilemma is that you need to add methods to the base class, but you can’t touch the base class. How do you get around this?

The design pattern that solves this kind of problem is called a “visitor” (the final one in the Design Patterns book), and it builds on the double dispatching scheme shown in the last section.

The visitor pattern allows you to extend the interface of the primary type by creating a separate class hierarchy of type Visitor to virtualize the operations performed upon the primary type. The objects of the primary type simply “accept” the visitor, then call the visitor’s dynamically-bound member function.

When you want to have function objects on union data types, you will need visitor pattern.

You might wonder what function objects and union data types are, then it’s worth reading http://www.ccs.neu.edu/home/matthias/htdc.html

Thanks for the awesome explanation of @Federico A. Ramponi , I just made this in java version. Hope it might be helpful.

Also just as @Konrad Rudolph pointed out, it’s actually a double dispatch using two concrete instances together to determine the run-time methods.

So actually there is no need to create a common interface for the operation executor as long as we have the operation interface properly defined.

 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"); } } 

As you expect, a common interface will bring us more clarity though it’s actually not the essential part in this pattern.

 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"); } } 

your question is when to know:

i do not first code with visitor pattern. i code standard and wait for the need to occur & then refactor. so lets say you have multiple payment systems that you installed one at a time. At checkout time you could have many if conditions (or instanceOf) , for example :

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

now imagine i had 10 payment methods, it gets kind of ugly. So when you see that kind of pattern occuring visitor comes in handly to seperate all that out and you end up calling something like this afterwards:

 new PaymentCheckoutVistor(paymentType).visit() 

You can see how to implement it from the number of examples here, im just showing you a usecase.

Давайте будем гением компьютера.