Шаблон посетителя - Visitor pattern

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

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

Обзор

Посетитель [1]шаблон дизайна - один из двадцати трех хорошо известных Шаблоны проектирования GoF которые описывают, как решать повторяющиеся проблемы проектирования для разработки гибкого и многократно используемого объектно-ориентированного программного обеспечения, то есть объектов, которые легче реализовать, изменить, протестировать и повторно использовать.

Какие проблемы может решить шаблон дизайна Visitor? [2]

  • Должна быть возможность определять новую операцию для (некоторых) классов структуры объекта без изменения классов.

Когда новые операции требуются часто, а структура объекта состоит из множества несвязанных классов, негибко добавлять новые подклассы каждый раз, когда требуется новая операция, потому что «[..] распределение всех этих операций между различными классами узлов приводит к системе, которую трудно понимать, поддерживать и изменять ". [1]

Какое решение описывает шаблон дизайна Visitor?

  • Определите отдельный объект (посетитель), реализующий операцию, выполняемую над элементами структуры объекта.
  • Клиенты просматривают структуру объекта и вызывают отправка операция принять (посетитель) на элементе - который «отправляет» (делегирует) запрос «принятому объекту посетителя». Затем объект посетителя выполняет операцию с элементом («посещает элемент»).

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

См. Также схему классов и последовательности UML ниже.

Определение

В Банда из четырех определяет Посетителя как:

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

Природа посетителя делает его идеальным шаблоном для подключения к общедоступным API, что позволяет его клиентам выполнять операции с классом, используя «посещающий» класс, без необходимости изменять источник.[3]

Использует

Перенос операций в классы посетителей выгоден, когда

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

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

Пример использования

Рассмотрим дизайн 2D системы автоматизированного проектирования (CAD) система. По своей сути, существует несколько типов для представления основных геометрических фигур, таких как круги, линии и дуги. Сущности упорядочены по слоям, а наверху иерархии типов находится чертеж, который представляет собой просто список слоев с некоторыми добавленными свойствами.

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

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

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

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

Структура

Схема классов и последовательности UML

Пример диаграммы классов UML и диаграммы последовательности для шаблона проектирования Visitor.[4]

В приведенном выше UML диаграмма классов, то ElementA класс не реализует новую операцию напрямую. ElementA реализует диспетчерская операция принять (посетитель) который «отправляет» (делегирует) запрос «принятому объекту посетителя» (visitor.visitElementA (это)). В Посетитель1 класс реализует операцию (visitElementA (e: ElementA)).
ЭлементB затем реализует принять (посетитель) отправив в visitor.visitElementB (это). В Посетитель1 класс реализует операцию (visitElementB (e: ElementB)).

В UML схема последовательности показывает взаимодействия во время выполнения: Клиент объект проходит элементы структуры объекта (ЭлементA, ЭлементB) и звонки принять (посетитель) на каждом элементе.
Во-первых, Клиент звонки принять (посетитель) наElementA, который вызывает visitElementA (это) на принятых посетитель объект. Сам элемент (это) передается в посетитель чтобы он мог "посетить" ElementA (вызов operationA ()).
После этого Клиент звонки принять (посетитель) наЭлементB, который вызывает visitElementB (это) на посетитель что "посещает" ЭлементB (звонки operationB ()).

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

Посетитель в LePUS3 (легенда )

Подробности

Шаблон посетителя требует язык программирования что поддерживает разовая отправка, как распространенные объектно-ориентированные языки (например, C ++, Ява, Болтовня, Цель-C, Быстрый, JavaScript, Python и C # ) делать. При этом условии рассмотрим два объекта, каждый из которых относится к некоторому типу класса; один называется элемент, а другой посетитель.

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

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

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

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

Таким образом, реализация посещение Метод выбирается на основе как динамического типа элемента, так и динамического типа посетителя. Это эффективно реализует двойная отправка. Для языков, объектные системы которых поддерживают множественную отправку, а не только единую отправку, например Common Lisp или же C # через Среда выполнения динамического языка (DLR) реализация шаблона посетителя значительно упрощена (он же динамический посетитель), позволяя использовать простую перегрузку функций для охвата всех посещаемых случаев. Динамический посетитель при условии, что он работает только с общедоступными данными, соответствует открытый / закрытый принцип (поскольку он не изменяет существующие структуры) и принцип единой ответственности (поскольку он реализует шаблон Visitor в отдельном компоненте).

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

Пример C #

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

пространство имен Википедия{	общественный учебный класс ВыражениеПечатьПосетитель	{		общественный пустота PrintLiteral(Буквальный буквальный)		{			Консоль.WriteLine(буквальный.Ценить);		}				общественный пустота PrintAddition(Добавление добавление)		{			двойной leftValue = добавление.Оставили.GetValue();			двойной rightValue = добавление.Правильно.GetValue();			вар сумма = добавление.GetValue();			Консоль.WriteLine("{0} + {1} = {2}", leftValue, rightValue, сумма);		}	}		общественный Абстрактные учебный класс Выражение	{			общественный Абстрактные пустота Принимать(ВыражениеПечатьПосетитель v);				общественный Абстрактные двойной GetValue();	}	общественный учебный класс Буквальный : Выражение	{		общественный двойной Ценить { получать; набор; }		общественный Буквальный(двойной ценить)		{			это.Ценить = ценить;		}				общественный отменять пустота Принимать(ВыражениеПечатьПосетитель v)		{			v.PrintLiteral(это);		}				общественный отменять двойной GetValue()		{			возвращаться Ценить;		}	}	общественный учебный класс Добавление : Выражение	{		общественный Выражение Оставили { получать; набор; }		общественный Выражение Правильно { получать; набор; }		общественный Добавление(Выражение оставили, Выражение верно)		{			Оставили = оставили;			Правильно = верно;		}				общественный отменять пустота Принимать(ВыражениеПечатьПосетитель v)		{			v.PrintAddition(это);		}				общественный отменять двойной GetValue()		{			возвращаться Оставили.GetValue() + Правильно.GetValue();			}	}	общественный статический учебный класс Программа	{		общественный статический пустота Главный(нить[] аргументы)		{			// Эмуляция 1 + 2 + 3			вар е = новый Добавление(				новый Добавление(					новый Буквальный(1),					новый Буквальный(2)				),				новый Буквальный(3)			);						вар печатьПосетитель = новый ВыражениеПечатьПосетитель();			е.Принимать(печатьПосетитель);		}	}}

Пример Smalltalk

В этом случае объект должен знать, как печатать себя в потоке. Посетитель здесь - это объект, а не поток.

«Нет синтаксиса для создания класса. Классы создаются путем отправки сообщений другим классам».WriteStream подкласс: #ExpressionPrinter    instanceVariableNames: ''    classVariableNames: ''    упаковка: "Википедия".ExpressionPrinter>> напишите: объект    "Делегирует действие объекту. Объект не должен быть особенным    учебный класс; ему нужно только понимать сообщение #putOn: "    объект положить: себя.    ^ объект.Объект подкласс: #Выражение    instanceVariableNames: ''    classVariableNames: ''    упаковка: "Википедия".Выражение подкласс: # Буквально    instanceVariableNames: 'ценить'    classVariableNames: ''    упаковка: "Википедия".Буквальный класс >> с: ценность    «Метод класса для создания экземпляра класса Literal»    ^ себя новый        ценить: ценность;        себя.Буквальный>> значение: ценность  «Установщик стоимости»  ценить := ценность.Буквальный>> положить: поток    «Литеральный объект умеет печатать сам себя»    поток nextPutAll: ценить asString.Выражение подкласс: #Добавление    instanceVariableNames: 'лево право'    classVariableNames: ''    упаковка: "Википедия".Добавление class >> left: а верно: б    «Метод класса для создания экземпляра класса Addition»    ^ себя новый        оставили: а;        верно: б;        себя.Добавление>> слева: выражение    «Сеттер слева»    оставили := выражение.Добавление>> справа: выражение    "Сеттер за право"    верно := выражение.Добавление>> положить: поток    «Объект Addition умеет печатать самого себя»    поток nextPut: $(.    оставили положить: поток.    поток nextPut: $+.    верно положить: поток.    поток nextPut: $).Объект подкласс: # Программа    instanceVariableNames: ''    classVariableNames: ''    упаковка: "Википедия".Программа>>главный    | выражение транслировать |    выражение := Добавление                    оставили: (Добавление                            оставили: (Буквальный с: 1)                            верно: (Буквальный с: 2))                    верно: (Буквальный с: 3).    транслировать := ExpressionPrinter на: (Нить новый: 100).    транслировать записывать: выражение.    Стенограмма Показать: транслировать содержание.    Стенограмма румянец.

Пример C ++

Источники

#включают <iostream>#включают <vector>учебный класс АннотацияДиспетчер;  // Вперед объявление AbstractDispatcherучебный класс Файл {  // Родительский класс для элементов (ArchivedFile, SplitFile и              // Извлеченный файл) общественный:  // Эта функция принимает объект любого класса, производного от  // AbstractDispatcher и должен быть реализован во всех производных классах  виртуальный пустота Принимать(АннотацияДиспетчер& диспетчер) = 0;};// Вперед объявляем определенные элементы (файлы) для отправкиучебный класс Архивный файл;учебный класс SplitFile;учебный класс Извлеченный файл;учебный класс АннотацияДиспетчер {  // Объявляет интерфейс для диспетчера общественный:  // Объявляем перегрузки для каждого типа файла для отправки  виртуальный пустота Отправлять(Архивный файл& файл) = 0;  виртуальный пустота Отправлять(SplitFile& файл) = 0;  виртуальный пустота Отправлять(Извлеченный файл& файл) = 0;};учебный класс Архивный файл : общественный Файл {  // Конкретный класс элемента # 1 общественный:  // Разрешается во время выполнения, вызывает перегруженную функцию диспетчера,  // соответствующий ArchivedFile.  пустота Принимать(АннотацияДиспетчер& диспетчер) отменять {    диспетчер.Отправлять(*это);  }};учебный класс SplitFile : общественный Файл {  // Конкретный класс элемента # 2 общественный:  // Разрешается во время выполнения, вызывает перегруженную функцию диспетчера,  // соответствующий SplitFile.  пустота Принимать(АннотацияДиспетчер& диспетчер) отменять {    диспетчер.Отправлять(*это);  }};учебный класс Извлеченный файл : общественный Файл {  // Конкретный класс элемента # 3 общественный:  // Разрешается во время выполнения, вызывает перегруженную функцию диспетчера,  // соответствующий ExtractedFile.  пустота Принимать(АннотацияДиспетчер& диспетчер) отменять {    диспетчер.Отправлять(*это);  }};учебный класс Диспетчер : общественный АннотацияДиспетчер {  // Реализует отправку всех                                                // вид элементов (файлов) общественный:  пустота Отправлять(Архивный файл&) отменять {    стандартное::cout << "отправка ArchivedFile" << стандартное::конец;  }  пустота Отправлять(SplitFile&) отменять {    стандартное::cout << "отправка SplitFile" << стандартное::конец;  }  пустота Отправлять(Извлеченный файл&) отменять {    стандартное::cout << "отправка извлеченного файла" << стандартное::конец;  }};int главный() {  Архивный файл archived_file;  SplitFile split_file;  Извлеченный файл извлеченный_файл;  стандартное::вектор<Файл*> файлы = {      &archived_file,      &split_file,      &извлеченный_файл,  };  Диспетчер диспетчер;  за (Файл* файл : файлы) {    файл->Принимать(диспетчер);  }}

Выход

диспетчеризация ArchivedFiledispatching SplitFiledispatching ExtractedFile

Пример перехода

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

Источники

упаковка главныйимпорт "fmt"тип Посетитель интерфейс {	visitWheel(колесо Колесо) нить	visitEngine(двигатель Двигатель) нить	visitBody(тело Тело) нить	visitCar(машина Машина) нить}тип элемент интерфейс {	Принимать(посетитель Посетитель) нить}тип Колесо структура {	имя нить}func (ш *Колесо) Принимать(посетитель Посетитель) нить {	возвращаться посетитель.visitWheel(*ш)}func (ш *Колесо) getName() нить {	возвращаться ш.имя}тип Двигатель структура{}func (е *Двигатель) Принимать(посетитель Посетитель) нить {	возвращаться посетитель.visitEngine(*е)}тип Тело структура{}func (б *Тело) Принимать(посетитель Посетитель) нить {	возвращаться посетитель.visitBody(*б)}тип Машина структура {	двигатель Двигатель	тело   Тело	колеса [4]Колесо}func (c *Машина) Принимать(посетитель Посетитель) нить {	элементы := []элемент{		&c.двигатель,		&c.тело,		&c.колеса[0],		&c.колеса[1],		&c.колеса[2],		&c.колеса[3],	}	res := посетитель.visitCar(*c)	за _, элем := классифицировать элементы {		res += элем.Принимать(посетитель)	}	возвращаться res}тип PrintVisitor структура{}func (pv *PrintVisitor) visitWheel(колесо Колесо) нить {	возвращаться fmt.Sprintln("посещение", колесо.getName(), "колесо")}func (pv *PrintVisitor) visitEngine(двигатель Двигатель) нить {	возвращаться fmt.Sprintln("двигатель посещения")}func (pv *PrintVisitor) visitBody(тело Тело) нить {	возвращаться fmt.Sprintln("тело посещения")}func (pv *PrintVisitor) visitCar(машина Машина) нить {	возвращаться fmt.Sprintln("машина для посещения")}/* выход:машина для посещениядвигатель посещениятело посещенияпосещение переднего левого колесапосещение переднего правого колесапосещение заднего левого колесапосещение заднего правого колеса*/func главный() {	машина := Машина{		двигатель: Двигатель{},		тело:   Тело{},		колеса: [4]Колесо{			{"передний левый"},			{"передний правый"},			{"назад влево"},			{"назад вправо"},		},	}	посетитель := PrintVisitor{}	res := машина.Принимать(&посетитель)	fmt.Println(res)}

Выход

посещение автомобиля, посещение двигателя, посещение кузова, посещение переднего левого колеса, посещение переднего правого колеса, посещение заднего левого колеса, посещение заднего правого колеса.

Пример Java

Следующий пример на языке Ява, и показывает, как можно распечатать содержимое дерева узлов (в данном случае описывающего компоненты автомобиля). Вместо создания Распечатать методы для каждого подкласса узла (Колесо, Двигатель, Тело, и Машина), один класс посетителей (CarElementPrintVisitor) выполняет требуемое действие печати. Поскольку разные подклассы узлов требуют немного разных действий для правильной печати, CarElementPrintVisitor отправляет действия на основе класса аргумента, переданного его посещение метод. CarElementDoVisitor, которая аналогична операции сохранения для другого формата файла, делает то же самое.

Диаграмма

UML-диаграмма примера шаблона посетителя с элементами автомобиля

Источники

импорт java.util.List;интерфейс CarElement {    пустота принимать(CarElementVisitor посетитель);}интерфейс CarElementVisitor {    пустота посещение(Тело тело);    пустота посещение(Машина машина);    пустота посещение(Двигатель двигатель);    пустота посещение(Колесо колесо);}учебный класс Колесо орудия CarElement {  частный окончательный Нить имя;  общественный Колесо(окончательный Нить имя) {      это.имя = имя;  }  общественный Нить getName() {      возвращаться имя;  }  @Override  общественный пустота принимать(CarElementVisitor посетитель) {      /*       * accept (CarElementVisitor) в колесных орудиях       * accept (CarElementVisitor) в CarElement, поэтому вызов       * принять обязательно во время выполнения. Это можно считать       * первая * отправка. Однако решение позвонить       * посещение (Колесо) (в отличие от посещения (Двигатель) и т. д.) может быть       * сделано во время компиляции, так как this известно при компиляции       * время быть Колесом. Более того, каждая реализация       * CarElementVisitor реализует визит (Колесо), который       * другое решение, которое принимается во время выполнения. Это может быть       * считается * второй * отправки.       */      посетитель.посещение(это);  }}учебный класс Тело орудия CarElement {  @Override  общественный пустота принимать(CarElementVisitor посетитель) {      посетитель.посещение(это);  }}учебный класс Двигатель орудия CarElement {  @Override  общественный пустота принимать(CarElementVisitor посетитель) {      посетитель.посещение(это);  }}учебный класс Машина орудия CarElement {    частный окончательный Список<CarElement> элементы;    общественный Машина() {        это.элементы = Список.из(            новый Колесо("передний левый"), новый Колесо("передний правый"),            новый Колесо("назад влево"), новый Колесо("назад вправо"),            новый Тело(), новый Двигатель()        );    }    @Override    общественный пустота принимать(CarElementVisitor посетитель) {        за (CarElement элемент : элементы) {            элемент.принимать(посетитель);        }        посетитель.посещение(это);    }}учебный класс CarElementDoVisitor орудия CarElementVisitor {    @Override    общественный пустота посещение(Тело тело) {        Система.из.println("Движение моего тела");    }    @Override    общественный пустота посещение(Машина машина) {        Система.из.println("Заводить мою машину");    }    @Override    общественный пустота посещение(Колесо колесо) {        Система.из.println("Пинаю меня" + колесо.getName() + " колесо");    }    @Override    общественный пустота посещение(Двигатель двигатель) {        Система.из.println("Запуск моего двигателя");    }}учебный класс CarElementPrintVisitor орудия CarElementVisitor {    @Override    общественный пустота посещение(Тело тело) {        Система.из.println(«Посещение тела»);    }    @Override    общественный пустота посещение(Машина машина) {        Система.из.println(«В гостях у машины»);    }    @Override    общественный пустота посещение(Двигатель двигатель) {        Система.из.println(«Выездной двигатель»);    }    @Override    общественный пустота посещение(Колесо колесо) {        Система.из.println(«В гостях» + колесо.getName() + " колесо");    }}общественный учебный класс ПосетительДемо {    общественный статический пустота главный(окончательный Нить[] аргументы) {        Машина машина = новый Машина();        машина.принимать(новый CarElementPrintVisitor());        машина.принимать(новый CarElementDoVisitor());    }}


Выход

Посещение переднего левого колесаПосещение переднего правого колесаПосещение заднего левого колесаПосещение заднего правого колесаПосещение телаПосещение двигателяПосмотр автомобиля

Пример Common Lisp

Источники

(defclass авто ()  ((элементы : initarg : элементы)))(defclass автозапчасти ()  ((имя : initarg :имя : initform "<безымянная-часть-автомобиля>")))(defmethod объект печати ((п автозапчасти) транслировать)  (объект печати (значение слота п 'имя) транслировать))(defclass колесо (автозапчасти) ())(defclass тело (автозапчасти) ())(defclass двигатель (автозапчасти) ())(defgeneric траверс (функция объект другой объект))(defmethod траверс (функция (а авто) другой объект)  (с прорезями (элементы) а    (долист (е элементы)      (веселье функция е другой объект))));; делать что-нибудь посещения;; поймать все(defmethod сделай что-нибудь (объект другой объект)  (формат т "не знаю, как ~ s и ~ s должны взаимодействовать ~%" объект другой объект));; посещение с участием колеса и целого числа(defmethod сделай что-нибудь ((объект колесо) (другой объект целое число))  (формат т "пинать колесо ~ s ~ s раз ~%" объект другой объект));; посещение с участием колеса и символа(defmethod сделай что-нибудь ((объект колесо) (другой объект символ))  (формат т "пнуть колесо ~ s символически с помощью символа ~ s ~%" объект другой объект))(defmethod сделай что-нибудь ((объект двигатель) (другой объект целое число))  (формат т "запуск двигателя ~ s ~ s раз ~%" объект другой объект))(defmethod сделай что-нибудь ((объект двигатель) (другой объект символ))  (формат т "запуск двигателя ~ s символическим символом ~ s ~%" объект другой объект))(позволять ((а (make-instance 'авто                        : элементы `(,(make-instance 'колесо :имя "переднее левое колесо")                                    ,(make-instance 'колесо :имя "передний правый")                                    ,(make-instance 'колесо :имя "заднее левое колесо")                                    ,(make-instance 'колесо :имя "заднее правое колесо")                                    ,(make-instance 'тело :имя "тело")                                    ,(make-instance 'двигатель :имя "двигатель")))))  ;; переход к элементам печати  ;; stream * standard-output * играет здесь роль другого объекта  (траверс #'Распечатать а * стандартный вывод *)  (Terpri) ;; распечатать новую строку  ;; переход с произвольным контекстом из другого объекта  (траверс #'сделай что-нибудь а 42)  ;; переход с произвольным контекстом из другого объекта  (траверс #'сделай что-нибудь а 'abc))

Выход

"переднее-левое колесо" "переднее-правое колесо" "заднее-правое колесо" "заднее-правое колесо" "корпус" "двигатель" пинающее колесо "переднее-левое колесо" 42-кратное ударное колесо "переднее-правое" -колесо "42-кратное ударное колесо" заднее-правое колесо "42-кратное ударное колесо" заднее-правое колесо "42 раза не знаю, как" тело "и 42 должны взаимодействовать, запускающий двигатель" двигатель "42-кратный ударное колесо" переднее-левое колесо " символически используя символ ABC, управляя колесом "переднее правое колесо", символически используя символ ABC, управляя колесом "заднее правое колесо", символически используя символ ABC, используя символ ABC, символически используя "тело" и ABC. взаимодействовать запускающий двигатель "двигатель" символически с помощью символа ABC

Примечания

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

(defmethod траверс (функция (а авто)) ;; другой объект удален  (с прорезями (элементы) а    (долист (е элементы)      (веселье функция е)))) ;; отсюда тоже  ;; ...  ;; альтернативный способ печати-траверса  (траверс (лямбда (о) (Распечатать о * стандартный вывод *)) а)  ;; альтернативный способ сделать что-то с  ;; элементы a и целое число 42  (траверс (лямбда (о) (сделай что-нибудь о 42)) а)

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

Пример Python

Python не поддерживает перегрузку методов в классическом смысле (полиморфное поведение в соответствии с типом переданных параметров), поэтому методы «посещения» для разных типов моделей должны иметь разные имена.

Источники

"""Пример шаблона посетителя."""из abc импорт ABCMeta, абстрактный методНЕ РЕАЛИЗОВАНА = «Вы должны реализовать это».учебный класс CarElement:    __metaclass__ = ABCMeta    @abstractmethod    def принимать(себя, посетитель):        поднимать NotImplementedError(НЕ РЕАЛИЗОВАНА)учебный класс Тело(CarElement):    def принимать(себя, посетитель):        посетитель.visitBody(себя)учебный класс Двигатель(CarElement):    def принимать(себя, посетитель):        посетитель.visitEngine(себя)учебный класс Колесо(CarElement):    def __в этом__(себя, имя):        себя.имя = имя    def принимать(себя, посетитель):        посетитель.visitWheel(себя)учебный класс Машина(CarElement):    def __в этом__(себя):        себя.элементы = [            Колесо("передний левый"), Колесо("передний правый"),            Колесо("назад влево"), Колесо("назад вправо"),            Тело(), Двигатель()        ]    def принимать(себя, посетитель):        за элемент в себя.элементы:            элемент.принимать(посетитель)        посетитель.visitCar(себя)учебный класс CarElementVisitor:    __metaclass__ = ABCMeta    @abstractmethod    def visitBody(себя, элемент):        поднимать NotImplementedError(НЕ РЕАЛИЗОВАНА)    @abstractmethod    def visitEngine(себя, элемент):        поднимать NotImplementedError(НЕ РЕАЛИЗОВАНА)    @abstractmethod    def visitWheel(себя, элемент):        поднимать NotImplementedError(НЕ РЕАЛИЗОВАНА)    @abstractmethod    def visitCar(себя, элемент):        поднимать NotImplementedError(НЕ РЕАЛИЗОВАНА)учебный класс CarElementDoVisitor(CarElementVisitor):    def visitBody(себя, тело):        Распечатать(«Движение моего тела».)    def visitCar(себя, машина):        Распечатать("Заводить мою машину".)    def visitWheel(себя, колесо):        Распечатать("Пинаю меня {} колесо.".формат(колесо.имя))    def visitEngine(себя, двигатель):        Распечатать("Запускаем мой двигатель".)учебный класс CarElementPrintVisitor(CarElementVisitor):    def visitBody(себя, тело):        Распечатать(«Посещение тела».)    def visitCar(себя, машина):        Распечатать(«В гостях у машины».)    def visitWheel(себя, колесо):        Распечатать("Посещение {} колесо.".формат(колесо.имя))    def visitEngine(себя, двигатель):        Распечатать("Посещение двигателя".)машина = Машина()машина.принимать(CarElementPrintVisitor())машина.принимать(CarElementDoVisitor())

Выход

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

Абстракция

Если кто-то использует Python 3 или выше, они могут сделать общую реализацию метода accept:

учебный класс Доступный:    def принимать(себя, посетитель):        искать = "посещение_" + тип(себя).__qualname__.заменять(".", "_")        возвращаться getattr(посетитель, искать)(себя)

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

Связанные шаблоны проектирования

  • Шаблон итератора - определяет принцип обхода, такой как шаблон посетителя, без различия типов в пройденных объектах
  • Церковная кодировка - родственная концепция из функционального программирования, в которой помеченные типы объединения / суммы могут быть смоделированы с использованием поведения "посетителей" для таких типов, что позволяет шаблону посетителя имитировать варианты и узоры.

Смотрите также

Рекомендации

  1. ^ а б Эрих Гамма, Ричард Хелм, Ральф Джонсон, Джон Влиссидес (1994). Паттерны проектирования: элементы объектно-ориентированного программного обеспечения многократного использования. Эддисон Уэсли. стр.331ff. ISBN  0-201-63361-2.CS1 maint: несколько имен: список авторов (связь)
  2. ^ «Шаблон дизайна посетителя - проблема, решение и применимость». w3sDesign.com. Получено 2017-08-12.
  3. ^ Реальный пример шаблона посетителя
  4. ^ «Шаблон дизайна посетителя - структура и взаимодействие». w3sDesign.com. Получено 2017-08-12.

внешняя ссылка