Из Википедии, бесплатной энциклопедии
Перейти к навигации Перейти к поиску

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

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

Обзор [ править ]

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

Какие проблемы может решить шаблон дизайна Visitor? [2]
  • Должна быть возможность определить новую операцию для (некоторых) классов структуры объекта без изменения классов.

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

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

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

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

Определение [ править ]

Банда Четырех определяет Visitor как:

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

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

Использует [ редактировать ]

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

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

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

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

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

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

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

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

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

Структура [ править ]

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

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

В приведенной выше 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()).

Диаграмма классов [ править ]

Посетитель на унифицированном языке моделирования (UML)
Посетитель в LePUS3 ( легенда )

Подробности [ править ]

Шаблон посетителя требует языка программирования, который поддерживает единую отправку , как это делают обычные объектно-ориентированные языки (такие как 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 )

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

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

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

См. Также [ править ]

  • Алгебраический тип данных
  • Двойная отправка
  • Множественная отправка
  • Функциональный объект

Ссылки [ править ]

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

Внешние ссылки [ править ]

  • Семейство шаблонов дизайна 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 на основе делегатов.
  • Шаблон дизайна посетителя