Эта статья требует дополнительных ссылок для проверки . ( январь 2014 г. ) ( Узнайте, как и когда удалить это сообщение-шаблон ) |
В объектно-ориентированном программировании и программное обеспечение , то посетитель шаблон дизайна представляет собой способ , отделяющий алгоритм от объекта структуры , на которой она работает. Практическим результатом этого разделения является возможность добавлять новые операции к существующим структурам объектов без изменения структур. Это один из способов следовать принципу открытого / закрытого .
По сути, посетитель позволяет добавлять новые виртуальные функции в семейство классов , не изменяя классы. Вместо этого создается класс посетителя, который реализует все соответствующие специализации виртуальной функции. Посетитель принимает ссылку на экземпляр в качестве входных данных и реализует цель посредством двойной отправки .
Обзор [ править ]
Шаблон проектирования Visitor [1] - один из двадцати трех хорошо известных шаблонов проектирования GoF, которые описывают, как решать повторяющиеся проблемы проектирования для разработки гибкого и многократно используемого объектно-ориентированного программного обеспечения, то есть объектов, которые легче реализовать, изменить, тестирование и повторное использование.
- Какие проблемы может решить шаблон дизайна Visitor? [2]
- Должна быть возможность определить новую операцию для (некоторых) классов структуры объекта без изменения классов.
Когда новые операции требуются часто, а структура объекта состоит из множества несвязанных классов, негибко добавлять новые подклассы каждый раз, когда требуется новая операция, потому что «[..] распределение всех этих операций между различными классами узлов приводит к системе, которая затруднена понимать, поддерживать и изменять ". [1]
- Какое решение описывает шаблон дизайна Visitor?
- Определите отдельный объект (посетитель), который реализует операцию, выполняемую над элементами структуры объекта.
- Клиенты просматривают структуру объекта и вызывают операцию отправки accept (посетитель) для элемента, которая «отправляет» (делегирует) запрос «принятому объекту посетителя». Затем объект посетителя выполняет операцию с элементом («посещает элемент»).
Это позволяет создавать новые операции независимо от классов структуры объекта путем добавления новых объектов посетителей.
См. Также схему классов и последовательности UML ниже.
Определение [ править ]
Банда Четырех определяет Visitor как:
Представляет [ing] операцию, выполняемую над элементами структуры объекта. Visitor позволяет вам определить новую операцию, не изменяя классы элементов, с которыми он работает.
Природа посетителя делает его идеальным шаблоном для подключения к общедоступным API, что позволяет его клиентам выполнять операции с классом, используя «посещающий» класс, без необходимости изменять источник. [3]
Использует [ редактировать ]
Перенос операций в классы посетителей выгоден, когда
- требуется много несвязанных операций над структурой объекта,
- классы, составляющие структуру объекта, известны и не должны изменяться,
- нужно часто добавлять новые операции,
- алгоритм включает несколько классов структуры объекта, но желательно управлять им в одном месте,
- алгоритм должен работать в нескольких независимых иерархиях классов.
Однако недостатком этого шаблона является то, что он затрудняет расширение иерархии классов, поскольку новые классы обычно требуют добавления нового visit
метода к каждому посетителю.
Пример использования [ править ]
Рассмотрим проект системы автоматизированного проектирования (САПР) 2D . По сути, существует несколько типов для представления основных геометрических фигур, таких как круги, линии и дуги. Сущности упорядочены по слоям, а наверху иерархии типов находится рисунок, который представляет собой просто список слоев с некоторыми добавленными свойствами.
Основная операция в этой иерархии типов - сохранение чертежа в системном файловом формате. На первый взгляд может показаться приемлемым добавить локальные методы сохранения ко всем типам в иерархии. Но также полезно иметь возможность сохранять чертежи в других форматах файлов. Добавление еще большего количества методов сохранения во множество различных форматов файлов вскоре загромождает относительно чистую исходную геометрическую структуру данных.
Наивный способ решить эту проблему - поддерживать отдельные функции для каждого формата файла. Такая функция сохранения будет принимать рисунок в качестве входных данных, просматривать его и кодировать в этот конкретный формат файла. Поскольку это делается для каждого добавляемого другого формата, накапливается дублирование между функциями. Например, сохранение формы круга в растровом формате требует очень похожего кода, независимо от того, какая конкретная растровая форма используется, и отличается от других примитивных форм. Случай с другими примитивными формами, такими как линии и многоугольники, аналогичен. Таким образом, код становится большим внешним циклом, проходящим через объекты, с большим деревом решений внутри цикла, запрашивающим тип объекта. Другая проблема с этим подходом заключается в том, что очень легко пропустить фигуру в одной или нескольких заставках или ввести новую примитивную фигуру,
Вместо этого можно применить шаблон посетителя. Он кодирует логическую операцию по всей иерархии в один класс, содержащий по одному методу для каждого типа. В примере САПР каждая функция сохранения будет реализована как отдельный подкласс Visitor. Это устранит все дублирование проверок типов и шагов обхода. Также компилятор будет жаловаться, если форма будет опущена.
Другой мотив - повторное использование итерационного кода. Например, перебор структуры каталогов можно реализовать с помощью шаблона посетителя. Это позволит выполнять поиск файлов, резервные копии файлов, удаление каталогов и т. Д. Путем реализации посетителя для каждой функции при повторном использовании итерационного кода.
Структура [ править ]
Схема классов и последовательности UML [ править ]
В приведенной выше UML диаграммы классов , то ElementA
класс не реализует новую операцию непосредственно. Вместо этого ElementA
реализует операцию отправки, accept(visitor)
которая «отправляет» (делегирует) запрос «принятому объекту посетителя» ( visitor.visitElementA(this)
). В Visitor1
класс реализует операцию ( visitElementA(e:ElementA)
).ElementB
затем реализует accept(visitor)
путем отправки в visitor.visitElementB(this)
. В Visitor1
класс реализует операцию ( visitElementB(e:ElementB)
).
В UML - диаграмма последовательности
показывает время выполнения взаимодействия: The Client
объект пересекает элементы структуры объекта ( ElementA,ElementB
) и вызовы accept(visitor)
на каждом элементе.
Во- первых, Client
звонки accept(visitor)
на ElementA
, который призывает visitElementA(this)
на общепринятом visitor
объекта. Сам элемент ( this
) передается visitor
объекту, чтобы он мог "посещать" ElementA
(вызывать operationA()
).
После этого Client
звонки accept(visitor)
на ElementB
, который призывает visitElementB(this)
на visitor
что «визиты» ElementB
(звонки operationB()
).
Диаграмма классов [ править ]
Подробности [ править ]
Шаблон посетителя требует языка программирования, который поддерживает единую отправку , как это делают обычные объектно-ориентированные языки (такие как C ++ , Java , Smalltalk , Objective-C , Swift , JavaScript , Python и C # ). При этом условии рассмотрим два объекта, каждый из которых относится к некоторому типу класса; один называется элементом , а другой - посетителем .
Посетителя объявляет visit
метод, который принимает элемент в качестве аргумента, для каждого класса элемента. Конкретные посетители являются производными от класса посетителей и реализуют эти visit
методы, каждый из которых реализует часть алгоритма, работающего со структурой объекта. Состояние алгоритма поддерживается локально конкретным классом посетителей.
Элемент объявляет accept
метод принять посетитель, принимая посетитель в качестве аргумента. Конкретные элементы , производные от класса элемента, реализуют accept
метод. В простейшей форме это не более чем вызов visit
метода посетителя . Составные элементы, которые поддерживают список дочерних объектов, обычно перебирают их, вызывая каждый дочерний accept
метод.
Клиент создает структуру объекта, прямо или косвенно, и конкретизирует посетитель конкретных. Когда должна быть выполнена операция, которая реализуется с использованием шаблона Visitor, он вызывает accept
метод элемента (ов) верхнего уровня.
Когда accept
метод вызывается в программе, его реализация выбирается на основе как динамического типа элемента, так и статического типа посетителя. Когда visit
вызывается связанный метод, его реализация выбирается на основе как динамического типа посетителя, так и статического типа элемента, как известно из реализации accept
метода, который совпадает с динамическим типом элемента. (В качестве бонуса, если посетитель не может обработать аргумент данного типа элемента, компилятор обнаружит ошибку.)
Таким образом, реализация visit
метода выбирается исходя как из динамического типа элемента, так и из динамического типа посетителя. Это эффективно реализует двойную отправку . Для языков, чьи объектные системы поддерживают множественную отправку, а не только одиночную, например Common Lisp или C # через среду выполнения динамического языка (DLR), реализация шаблона посетителя значительно упрощается (он же динамический посетитель), позволяя использовать простую перегрузку функций для охватить все посещаемые дела. Динамический посетитель, при условии, что он работает только с общедоступными данными, соответствует принципу открытого / закрытого (поскольку он не изменяет существующие структуры) и принципу единой ответственности. (поскольку он реализует шаблон Visitor в отдельном компоненте).
Таким образом, можно написать один алгоритм для обхода графа элементов, и во время этого обхода можно выполнять множество различных операций, предоставляя различные типы посетителей для взаимодействия с элементами на основе динамических типов как элементов, так и посетители.
Пример C # [ править ]
В этом примере объявляется отдельный ExpressionPrintingVisitor
класс, отвечающий за печать.
пространство имен Википедия { public class ExpressionPrintingVisitor { public void PrintLiteral ( Literal literal ) { Console . WriteLine ( букв . Значение ); }public void PrintAddition ( добавление добавления ) { двойное leftValue = добавление . Слева . GetValue (); двойное rightValue = сложение . Верно . GetValue (); var sum = сложение . GetValue (); Консоль . WriteLine ( "{0} + {1} = {2}" , leftValue , rightValue , sum ); } }публичный абстрактный класс Expression { публичный абстрактный недействительный Принятие ( ExpressionPrintingVisitor v );публичный абстрактный двойной GetValue (); }публичный класс Литерал : Выражение { публичное двойное значение { получить ; набор ; }public Literal ( двойное значение ) { this . Значение = значение ; }public override void Accept ( ExpressionPrintingVisitor v ) { v . PrintLiteral ( это ); }общедоступное переопределение двойного GetValue () { возвращаемое значение ; } }общедоступный класс Дополнение : Выражение { публичное выражение Лево { получить ; набор ; } общественное выражение Право { получить ; набор ; }общедоступное дополнение ( Выражение слева , Выражение справа ) { Left = left ; Right = право ; }public override void Accept ( ExpressionPrintingVisitor v ) { v . PrintAddition ( это ); }public override double GetValue () { return Left . GetValue () + Вправо . GetValue (); } }public static class Program { public static void Main ( string [] args ) { // Эмуляция 1 + 2 + 3 var e = new Addition ( new Addition ( new Literal ( 1 ), new Literal ( 2 ) ), new Literal ( 3 ) );var PrintingVisitor = новый ExpressionPrintingVisitor (); е . Принять ( печать посетителя ); } } }
Пример Smalltalk [ править ]
В этом случае объект должен знать, как печатать себя в потоке. Посетитель здесь - это объект, а не поток.
«Нет синтаксиса для создания класса. Классы создаются путем отправки сообщений другим классам». Подкласс WriteStream : #ExpressionPrinter instanceVariableNames: '' classVariableNames: '' package: 'Wikipedia' .ExpressionPrinter >> write: anObject " Делегирует действие объекту. Объект не обязательно должен принадлежать к какому-либо специальному классу; ему нужно только понимать сообщение #putOn:" anObject putOn: self . ^ anObject . Подкласс объекта : #Expression instanceVariableNames: '' classVariableNames: '' package: 'Wikipedia' . Подкласс выражения : #Literal instanceVariableNames: 'value' classVariableNames: '' package: 'Wikipedia' .Literal class >> with: aValue "Метод класса для создания экземпляра класса Literal" ^ собственное новое значение: aValue ; себя .Литерал >> значение: aValue " Установщик значения" value : = aValue .Literal >> putOn: aStream "Объект Literal знает, как напечатать себя" aStream nextPutAll: value asString . Подкласс выражения : #Addition instanceVariableNames: 'left right' classVariableNames: '' package: 'Wikipedia' . Класс добавления >> left: a right: b «Метод класса для создания экземпляра класса Addition» ^ self new left: a ; справа: b ; себя .Дополнение >> left: выражение « Установщик для левого» left : = anExpression .Дополнение >> справа: выражение " Установщик права" справа : = Выражение .Сложение >> putOn: aStream "объект капельной умеет печатать себя" aStream nextPut: $ ( . Осталось putOn: aStream . AStream nextPut: $ + . Право putOn: aStream . AStream nextPut: $) . Подкласс объекта : #Program instanceVariableNames: '' classVariableNames: '' package: 'Wikipedia' .Программа >> главная | поток выражений | выражение : = Сложение слева: ( Сложение слева: ( Литерал с: 1 ) справа: ( Литерал с: 2 )) справа: ( Литерал с: 3 ) . stream : = ExpressionPrinter on: ( Новая строка : 100 ) . запись потока : выражение . Стенограмма шоу: содержимое потока . Стенограмма вровень .
Пример C ++ [ править ]
Источники [ править ]
#include <iostream>#include <вектор>class AbstractDispatcher ; // Вперед объявление AbstractDispatcherclass File { // Родительский класс для элементов (ArchivedFile, SplitFile и // ExtractedFile ) public : // Эта функция принимает объект любого класса, производного от // AbstractDispatcher, и должна быть реализована во всех производных классах virtual void Accept ( AbstractDispatcher & диспетчер ) = 0 ; };// Вперед объявляем определенные элементы (файлы) для отправки class ArchivedFile ; class SplitFile ; class ExtractedFile ;class AbstractDispatcher { // Объявляет интерфейс диспетчера public : // Объявляет перегрузки для каждого типа файла для отправки виртуального void Dispatch ( ArchivedFile & file ) = 0 ; виртуальная пустота отправка ( SplitFile & файла ) = 0 ; виртуальная недействительная отправка ( ExtractedFile & file ) = 0 ; };class ArchivedFile : public File { // Конкретный элемент class # 1 public : // Разрешается во время выполнения, он вызывает перегруженную функцию диспетчера, // соответствующую ArchivedFile. void Accept ( AbstractDispatcher & dispatcher ) переопределить { dispatcher . Отправка ( * это ); } };class SplitFile : public File { // Конкретный элемент class # 2 public : // Разрешается во время выполнения, он вызывает перегруженную функцию диспетчера, // соответствующую SplitFile. void Accept ( AbstractDispatcher & dispatcher ) переопределить { dispatcher . Отправка ( * это ); } };class ExtractedFile : public File { // Конкретный элемент class # 3 public : // Разрешается во время выполнения, он вызывает перегруженную функцию диспетчера, // соответствующую ExtractedFile. void Accept ( AbstractDispatcher & dispatcher ) переопределить { dispatcher . Отправка ( * это ); } };class Dispatcher : public AbstractDispatcher { // Реализует диспетчеризацию всех // типов элементов (файлов) public : void Dispatch ( ArchivedFile & ) override { std :: cout << "диспетчеризация ArchivedFile" << std :: endl ; } void Dispatch ( SplitFile & ) override { std :: cout << "отправка SplitFile" << std :: endl ; } void Dispatch ( ExtractedFile & ) override { std :: cout << "отправка ExtractedFile" << std :: endl ; } };int main () { ArchivedFile archived_file ; SplitFile split_file ; ExtractedFile Extracted_file ; std :: vector < Файл *> files = { & archived_file , & split_file , & extract_file , }; Диспетчер диспетчер ; для ( Файл * файл : файлы ) { файл -> Принять ( диспетчер ); } }
Вывод [ править ]
отправка ArchivedFileотправка SplitFileотправка ExtractedFile
Пример Go [ править ]
Go не поддерживает перегрузку, поэтому методы посещения должны иметь разные имена.
Источники [ править ]
основной пакетимпорт "FMT"type Интерфейс посетителя { visitWheel ( колесо колеса ) строка visitEngine ( двигатель Двигатель ) строка visitBody ( body Body ) строка visitCar ( автомобиль Автомобиль ) строка } type element interface { Accept ( посетитель Visitor ) строка }type Wheel struct { name string }func ( w * Wheel ) Принять ( посетитель Посетитель ) строка { вернуть посетителя . visitWheel ( * w ) }func ( w * Wheel ) getName () string { return w . name }тип Engine struct {}func ( e * Engine ) Принять ( посетитель Посетитель ) строка { вернуть посетителя . visitEngine ( * e ) }тип Структура тела {}func ( b * Body ) Принять ( посетитель Посетитель ) строка { вернуть посетителя . visitBody ( * b ) }тип Структура автомобиля { двигатель Корпус двигателя Кузов колеса [ 4 ] Колесо } func ( c * Car ) Accept ( посетитель Посетитель ) string { elements : = [] element { & c . двигатель , и гр . тела , и гр . колеса [ 0 ] и т . д. колеса [ 1 ] и т . д. колеса [ 2 ] и т . д. колеса [ 3 ],} res : = посетитель . visitCar ( * c ) для _ , elem : = range elements { res + = elem . Принять ( посетитель ) } вернуть res }тип PrintVisitor struct {}func ( pv * PrintVisitor ) visitWheel ( колесо колеса ) строка { return fmt . Sprintln ( "посещение" , колеса . GetName (), "колесо" ) } функ ( р * PrintVisitor ) visitEngine ( двигатель двигатель ) строка { возвращение FMT . Sprintln ( "двигатель посещения" ) } func (pv * PrintVisitor ) visitBody ( body Body ) строка { return fmt . Sprintln ( "посещение тела" ) } func ( pv * PrintVisitor ) visitCar ( автомобиль Автомобиль ) строка { return fmt . Sprintln ( "машина гостя" ) }/ * вывод: посещение машины посещение двигателя посещение тела посещение переднего левого колеса посещение переднего правого колеса посещение заднего левого колеса посещение заднего правого колеса * / func main () { car : = Car { engine : Engine {}, body : Body {}, колеса : [ 4 ] Колесо { { "переднее левое" }, { "переднее правое" }, { "заднее левое" }, { "заднее правое" },}, }посетитель : = PrintVisitor {} res : = car . Принять ( и посетитель ) fmt . Println ( res ) }
Вывод [ править ]
машина для посещениядвигатель посещенияпосещающий органпосещение переднего левого колесапосещение переднего правого колесапосещение заднего левого колесапосещение заднего правого колеса
Пример Java [ править ]
Следующий пример написан на языке Java и показывает, как можно распечатать содержимое дерева узлов (в данном случае описывающего компоненты автомобиля). Вместо того чтобы создавать print
методы для каждого подкласса узла ( Wheel
, Engine
, Body
и Car
), один класс посетителя ( CarElementPrintVisitor
) выполняет требуемое действие печати. Поскольку разные подклассы узла требуют немного разных действий для правильной печати, CarElementPrintVisitor
отправляет действия на основе класса аргумента, переданного его visit
методу. CarElementDoVisitor
аналогично операции сохранения для другого формата файла.
Диаграмма [ править ]
Источники [ править ]
import java.util.List ;интерфейс CarElement { void accept ( посетитель CarElementVisitor ); }interface CarElementVisitor { void visit ( тело тела ); аннулируются визит ( Car автомобиль ); недействительный визит ( Двигатель двигатель ); недействительный визит ( Колесо колесо ); }класс Wheel реализует CarElement { частное конечное имя строки ; public Wheel ( последнее имя строки ) { this . name = name ; } общедоступная строка getName () { возвращаемое имя ; } @Override public void accept ( CarElementVisitor visitor ) { / * * accept (CarElementVisitor) в Wheel реализует * accept (CarElementVisitor) в CarElement, поэтому вызов * accept привязан во время выполнения. Это можно считать * первой * отправкой. Однако решение вызвать * visit (Wheel) (в отличие от visit (Engine) и т. Д.) Может быть * принято во время компиляции, поскольку во время компиляции this известно * как Wheel. Более того, каждая реализация * CarElementVisitor реализует посещение (Колесо), которое * является другим решением, которое принимается во время выполнения. Это может быть * считается * второй * отправки. * / посетитель . посетить ( это ); } }class Body реализует CarElement { @Override public void accept ( CarElementVisitor visitor ) { посетитель . посетить ( это ); } }class Engine реализует CarElement { @Override public void accept ( CarElementVisitor visitor ) { посетитель . посетить ( это ); } }Класс автомобиль реализует CarElement { частного окончательного лист < CarElement > элементы ; public Car () { this . elements = Список . из ( новое колесо ( «переднее левое» ), новое колесо ( «переднее правое» ), новое колесо ( «заднее левое» ), новое колесо ( «заднее правое» ), новое тело (), новый двигатель () ); } @Override public void accept ( CarElementVisitor visitor ) { for ( CarElement element : elements ) { element . принять ( посетитель ); } посетитель . посетить ( это ); } }Класс CarElementDoVisitor реализует CarElementVisitor { @Override public void visit ( Body body ) { System . из . println ( "Движение моего тела" ); } @Override общественности недействительным визит ( Car автомобиль ) { System . из . println ( «Завожу машину» ); } @Override public void visit ( Wheel wheel ) { System . из . println ( «Пинаю меня» + колесо . getName () + «колесо» ); } @Override общественности недействительного визита ( двигатель двигатель ) { System . из . println ( "Запуск двигателя" ); } }Класс CarElementPrintVisitor реализует CarElementVisitor { @Override public void visit ( Body body ) { System . из . println ( "Тело посещения" ); } @Override общественности недействительным визит ( Car автомобиль ) { System . из . println ( «Машина в гостях» ); } @Override общественности недействительного визита ( двигатель двигатель ) { System . из . println ( " Система посещений" ); } @Override public void visit ( Wheel wheel ) { System . из . println ( «Посещение» + колесо . getName () + «колесо» ); } }открытый класс VisitorDemo { общедоступный статический void main ( final String [] args ) { Автомобиль car = new Car (); автомобиль . accept ( новый CarElementPrintVisitor ()); автомобиль . accept ( новый CarElementDoVisitor ()); } }
Вывод [ править ]
Посещение переднего левого колесаПосещение переднего правого колесаПосещение заднего левого колесаПосещение заднего правого колесаПосещение телаПосещение двигателяВ гостях у машиныПинаю мое переднее левое колесоПинаю мое переднее правое колесоПинаю заднее левое колесоПинаю заднее правое колесоДвижение моего телаЗапуск моего двигателяЗаводится моя машина
Пример Common Lisp [ править ]
Источники [ править ]
( defclass auto () (( элементы : initarg : элементы )))( defclass auto-part () (( name : initarg : name : initform "<безымянная-автомобильная-часть>" )))( defmethod print-object (( p auto-part ) stream ) ( print-object ( значение слота p 'имя ) поток ))( колесо дефекласса ( автозапчасти ) ()) ( defclass body ( автозапчасть ) ())( defclass engine ( автозапчасти ) ())( defgeneric traverse ( объект функции другой объект )) ( defmethod traverse ( function ( a auto ) other-object ) ( with-slots ( elements ) a ( dolist ( e elements ) ( funcall function e other-object ))));; сделать что-нибудь посещения;; поймать все ( defmethod do-something ( объект- другой-объект ) ( формат t "не знаю, как ~ s и ~ s должны взаимодействовать ~%" объект- другой-объект ));; посещение с использованием колеса и целого числа ( defmethod do-something (( object wheel ) ( other-object integer )) ( формат t "пинать колесо ~ s ~ s раз ~%" объект другой-объект ));; посещение с участием колеса и символа ( defmethod do-something (( object wheel ) ( other-object symbol )) ( формат t "пинать колесо ~ s символически, используя символ ~ s ~%" объект другой-объект ))( defmethod do-something (( объектный движок ) ( другой объект целое )) ( формат t "запуск двигателя ~ s ~ s раз ~%" объект другой объект ))( defmethod do-something (( объектный движок ) ( символ другого объекта )) ( формат t "запускает engine ~ s символически, используя символ ~ s ~%" объект другой объект )) ( let (( a ( make-instance 'auto : elements ` ( , ( make-instance ' wheel : name " front-left-wheel " ) , ( make-instance 'wheel : name " front-right-wheel " ) , ( make-instance 'wheel : name «заднее-левое-колесо» ) , ( make-instance ' wheel : name «заднее-правое-колесо» ) , ( make-instance 'body : name «body») ,( make-instance 'engine : name "engine" ))))) ;; переход к элементам печати ;; stream * standard-output * играет здесь роль другого объекта ( traverse # ' print a * standard-output * ) ( терпи ) ;; распечатать новую строку ;; переход с произвольным контекстом из другого объекта ( traverse # ' do-something a 42 ) ;; переход с произвольным контекстом из другого объекта ( traverse # ' do-something a ' abc ))
Вывод [ править ]
"переднее левое колесо""переднее правое колесо""заднее левое колесо""заднее правое колесо""тело""двигатель"пнуть колесо "переднее-левое колесо" 42 разапинать колесо "переднее-правое" 42 разапнуть колесо "заднее левое колесо" 42 разапинать колесо "заднее-правое" 42 разане знаю, как "тело" и 42 должны взаимодействоватьзапуск двигателя «двигатель» 42 разапинать колесо "переднее левое колесо" символически с помощью символа ABCпинать колесо "переднее правое колесо" символически с помощью символа ABCпнуть колесо "заднее левое колесо" символически с помощью символа ABCпинать колесо "заднее правое колесо" символически с помощью символа ABCне знаю, как "тело" и ABC должны взаимодействоватьзапуск двигателя "двигатель" символически с использованием символа ABC
Заметки [ править ]
other-object
Параметр является излишним traverse
. Причина в том, что можно использовать анонимную функцию, которая вызывает желаемый целевой метод с лексически захваченным объектом:
( defmethod traverse ( function ( a auto )) ;; другой объект удален ( with-slots ( elements ) a ( dolist ( e elements ) ( funcall function e )))) ;; отсюда тоже ;; ... ;; альтернативный способ печати-траверса ( traverse ( lambda ( o ) ( print o * standard-output * )) a ) ;; альтернативный способ сделать что-то с ;; элементы a и целого числа 42 ( traverse ( lambda ( o ) ( do-something o 42 )) a )
Теперь множественная отправка происходит в вызове, исходящем из тела анонимной функции, и поэтому traverse
это просто функция сопоставления, которая распределяет приложение функции по элементам объекта. Таким образом, все следы паттерна посетителя исчезают, за исключением функции сопоставления, в которой нет свидетельств участия двух объектов. Все сведения о двух объектах и об их типах содержатся в лямбда-функции.
Пример Python [ править ]
Python не поддерживает перегрузку методов в классическом смысле (полиморфное поведение в соответствии с типом переданных параметров), поэтому методы «посещения» для разных типов моделей должны иметь разные имена.
Источники [ править ]
"" " Пример шаблона посетителя. " ""from abc import ABCMeta , abstractmethodNOT_IMPLEMENTED = "Вы должны реализовать это."class CarElement : __metaclass__ = ABCMeta @abstractmethod def accept ( self , visitor ): поднять NotImplementedError ( NOT_IMPLEMENTED )class Body ( CarElement ): def accept ( self , visitor ): посетитель . visitBody ( сам )class Engine ( CarElement ): def accept ( self , visitor ): посетитель . visitEngine ( самостоятельно )class Wheel ( CarElement ): def __init__ ( self , name ): self . name = name def accept ( self , visitor ): посетитель . visitWheel ( самостоятельно )класс Car ( CarElement ): def __init__ ( self ): self . elements = [ Wheel ( «переднее левое» ), Wheel ( «переднее правое» ), Wheel ( «заднее левое» ), Wheel ( «заднее правое» ), Body (), Engine () ] def accept ( self , visitor ): для элемента в себе . элементы : элемент . принять ( посетителя ) посетителя . visitCar ( самостоятельно )Класс CarElementVisitor : __metaclass__ = ABCMeta @abstractmethod Защиту visitBody ( самостоятельно , элемент ): повышение NotImplementedError ( NOT_IMPLEMENTED ) @abstractmethod Защита visitEngine ( самостоятельно , элемент ): повышение NotImplementedError ( NOT_IMPLEMENTED ) @abstractmethod Защита visitWheel ( самостоятельно , элемент ): повышение NotImplementedError (NOT_IMPLEMENTED ) @abstractmethod def visitCar ( self , element ): поднять NotImplementedError ( NOT_IMPLEMENTED )class CarElementDoVisitor ( CarElementVisitor ): def visitBody ( self , body ): print ( «Перемещение моего тела.» ) def visitCar ( self , car ): print ( «Запуск машины» ) def visitWheel ( self , wheel ): print ( "Ударь мой {} колесо." . формат ( колесо . название )) защиту visitEngine( self , engine ): print ( «Запуск двигателя» )class CarElementPrintVisitor ( CarElementVisitor ): def visitBody ( self , body ): print ( «Посетившее тело.» ) def visitCar ( self , car ): print ( « Визитная машина.» ) def visitWheel ( self , wheel ): print ( «Посещение {} wheel. " . format ( wheel . name )) def visitEngine( self , engine ): print ( "Система посещений." )car = Автомобиль () автомобиль . accept ( CarElementPrintVisitor ()) автомобиль . accept ( CarElementDoVisitor ())
Вывод [ править ]
Посещение переднего левого колеса.Посещение переднего правого колеса.Посещение заднего левого колеса.Посещение заднего правого колеса.Посещение тела.Посещение двигателя.В гостях у машины.Удар ногой по переднему левому колесу.Удар ногой по переднему правому колесу.Удар ногой по заднему левому колесу.Пинаю заднее правое колесо.Двигаясь своим телом.Запускаю двигатель.Заводится моя машина.
Абстракция [ править ]
Если кто-то использует Python 3 или выше, они могут сделать общую реализацию метода accept:
class Visitable : def accept ( self , visitor ): lookup = "visit_" + type ( self ) . __qualname__ . replace ( "." , "_" ) return getattr ( visitor , lookup ) ( self )
Можно расширить это, чтобы перебрать порядок разрешения методов класса, если они хотят вернуться к уже реализованным классам. Они также могут использовать функцию перехвата подкласса, чтобы заранее определить поиск.
Связанные шаблоны проектирования [ править ]
- Шаблон итератора - определяет принцип обхода, такой как шаблон посетителя, без дифференциации типов в пройденных объектах.
- Кодирование Чёрча - связанная концепция функционального программирования, в которой помеченные типы объединения / суммы могут быть смоделированы с использованием поведения «посетителей» для таких типов и которая позволяет шаблону посетителя имитировать варианты и шаблоны .
См. Также [ править ]
- Алгебраический тип данных
- Двойная отправка
- Множественная отправка
- Функциональный объект
Ссылки [ править ]
- ^ a b Эрих Гамма, Ричард Хелм, Ральф Джонсон, Джон Влиссидес (1994). Шаблоны проектирования: элементы объектно-ориентированного программного обеспечения многократного использования . Эддисон Уэсли. стр. 331 и далее . ISBN 0-201-63361-2.CS1 maint: несколько имен: список авторов ( ссылка )
- ^ «Шаблон дизайна посетителя - проблема, решение и применимость» . w3sDesign.com . Проверено 12 августа 2017 .
- ^ Пример посетителя из реальной жизни
- ^ «Шаблон дизайна посетителя - структура и сотрудничество» . w3sDesign.com . Проверено 12 августа 2017 .
Внешние ссылки [ править ]
Викискладе есть медиафайлы, связанные с шаблоном посетителя . |
В Wikibook Computer Science Design Patterns есть страница по теме: Реализации посетителей на разных языках |
- Семейство шаблонов дизайна Visitor на Wayback Machine (архивировано 22 октября 2015 г.). Дополнительные архивы: 12 апреля 2004 , 5 марта 2002 . Примерная глава из книги «Принципы, шаблоны и практики гибкой разработки программного обеспечения» , Роберт К. Мартин , Прентис Холл.
- Шаблон посетителя в UML и LePUS3 (язык описания дизайна)
- Статья " компонентная: Посетитель Пример по Бертрану Мейер и Карине Арнаут, Computer (IEEE), том 39, № 7, июль 2006, стр 23-30...
- Статья A. Теоретико-типовая реконструкция паттерна посетителей
- Статья Йенса Палсберга и К. Барри Джея «Суть паттерна посетителя » . Документ IEEE-CS COMPSAC 1997 года, показывающий, что методы accept () не нужны, когда доступно отражение; вводит термин «прогулка» для техники.
- Статья Брюса Уоллеса « Время для размышлений » с подзаголовком «Возможности отражения в Java 1.2 устраняют обременительные методы accept () из вашего шаблона посетителя»
- Шаблоны посетителей как универсальная модель завершения вычислений.
- Шаблон посетителя с использованием отражения (java).
- PerfectJPattern Open Source Project , предоставляет контекстную и безопасную для типов реализацию шаблона посетителей в Java на основе делегатов.
- Шаблон дизайна посетителя