stringtranslate.com

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

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

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

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

Обзор

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

Какие проблемы может решить шаблон проектирования «Посетитель»?

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

Какое решение описывает шаблон проектирования «Посетитель»?

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

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

Определение

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

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

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

Преимущества

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

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

Приложение

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

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

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

Вместо этого можно применить шаблон visitor. Он кодирует логическую операцию (т. е. save(image_tree)) по всей иерархии в один класс (т. е. Saver), который реализует общие методы обхода дерева и описывает виртуальные вспомогательные методы (т. е. save_circle, save_square и т. д.), которые должны быть реализованы для форматно-специфических поведений. В случае примера CAD такие форматно-специфические поведения будут реализованы подклассом Visitor (т. е. SaverPNG). Таким образом, все дублирование проверок типов и шагов обхода удаляется. Кроме того, компилятор теперь жалуется, если пропущена форма, поскольку теперь она ожидается общей базовой функцией обхода/сохранения.

Итерационные циклы

Шаблон посетителя может использоваться для итерации по структурам данных, подобным контейнерам , как и шаблон Итератора , но с ограниченной функциональностью. [3] : 288  Например, итерация по структуре каталога может быть реализована классом функций вместо более обычного шаблона цикла . Это позволило бы извлекать различную полезную информацию из содержимого каталогов, реализуя функциональность посетителя для каждого элемента при повторном использовании кода итерации. Он широко используется в системах Smalltalk и может быть найден также в C++. [3] : 289  Однако недостатком этого подхода является то, что вы не можете легко выйти из цикла или выполнять итерацию одновременно (параллельно, т. е. проходя по двум контейнерам одновременно с помощью одной iпеременной). [3] : 289  Последнее потребовало бы написания дополнительной функциональности для посетителя для поддержки этих функций. [3] : 289 

Структура

Диаграмма классов и последовательностей UML

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

В приведенной выше диаграмме классов UML класс не реализует новую операцию напрямую. Вместо этого реализует операцию диспетчеризации , которая «диспетчеризует» (делегирует) запрос «принятому объекту посетителя» ( ). Класс реализует операцию ( ). затем реализует путем диспетчеризации в . Класс реализует операцию ( ).ElementAElementA accept(visitor)visitor.visitElementA(this)Visitor1visitElementA(e:ElementA)
ElementBaccept(visitor)visitor.visitElementB(this)Visitor1visitElementB(e:ElementB)

Диаграмма последовательности UML показывает взаимодействия во время выполнения: Объект проходит по элементам структуры объекта ( ) и вызывает каждый элемент. Сначала вызывается , который вызывает принятый объект. Сам элемент ( ) передается в , чтобы он мог «посетить» (вызвать ). После этого вызываются , который вызывает , который « посещает» (вызывает ).ClientElementA,ElementBaccept(visitor)
Clientaccept(visitor)ElementAvisitElementA(this)visitorthisvisitorElementAoperationA()
Clientaccept(visitor)ElementBvisitElementB(this)visitorElementBoperationB()

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

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

Подробности

Шаблон visitor требует языка программирования , который поддерживает одиночную диспетчеризацию , как это делают обычные объектно-ориентированные языки (такие как C++ , Java , Smalltalk , Objective-C , Swift , JavaScript , Python и C# ). При этом условии рассмотрим два объекта, каждый из которых принадлежит некоторому типу класса; один называется element , а другой — visitor .

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

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

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

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

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

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

Пример на С#

В этом примере объявляется отдельный ExpressionPrintingVisitorкласс, который отвечает за печать. Если требуется введение нового конкретного посетителя, будет создан новый класс для реализации интерфейса Visitor, а также будут предоставлены новые реализации для методов Visit. Существующие классы (Literal и Addition) останутся без изменений.

с использованием Системы ; пространство имен Википедия ; открытый интерфейс Посетитель { void Visit ( Литерал литерал ); void Visit ( Дополнение дополнение ); }         public class ExpressionPrintingVisitor : Visitor { public void Visit ( Literal literal ) { Console . WriteLine ( literal . Value ); }            public void Visit ( Addition addition ) { double leftValue = addition.Left.GetValue ( ) ; double rightValue = addition.Right.GetValue ( ) ; var sum = addition.GetValue ( ) ; Console.WriteLine ( " {0} + {1} = {2} " , leftValue , rightValue , sum ) ; } }                      public abstract class Expression { public abstract void Accept ( Visitor v ); public abstract double GetValue (); }             public class Literal : Expression { public Literal ( double value ) { this.Value = value ; }             публичное двойное значение { получить ; установить ; }       public override void Accept ( Visitor v ) { v.Visit ( this ) ; } public override double GetValue ( ) { return Value ; } }                public class Addition : Expression { public Addition ( Expression left , Expression right ) { Left = left ; Right = right ; }                  публичное выражение слева { получить ; установить ; } публичное выражение справа { получить ; установить ; }              public override void Accept ( Visitor v ) { Left.Accept ( v ) ; Right.Accept ( v ) ; v.Visit ( this ) ; } public override double GetValue ( ) { return Left.GetValue ( ) + Right.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 = new ExpressionPrintingVisitor ( ); e.Accept ( printingVisitor ) ; Console.ReadKey ( ) ; } }       

Пример Smalltalk

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

"Синтаксиса для создания класса нет. Классы создаются путем отправки сообщений другим классам." WriteStream  подкласс:  #ExpressionPrinter  instanceVariableNames:  ''  classVariableNames:  ''  package:  'Wikipedia' .ExpressionPrinter >>write:  anObject  "Делегирует действие объекту. Объект не обязательно должен относиться к какому-либо специальному  классу; он должен только уметь понимать сообщение #putOn:"  anObject  putOn:  self .  ^  anObject .Подкласс объекта  :  #Expression  instanceVariableNames:  ''  classVariableNames:  ''  package:  'Wikipedia' .Подкласс выражения  :  #Литеральный  экземплярVariableNames:  'value'  classVariableNames:  ''  package:  'Wikipedia' .Класс Literal  >>with:  aValue  "Метод класса для построения экземпляра класса Literal"  ^  self  new  value:  aValue ;  yourself .Литерал >>value:  aValue  "Сеттер для значения"  value  :=  aValue .Literal >>putOn:  aStream  "Объект Literal знает, как печатать себя"  aStream  nextPutAll:  value  asString .Подкласс выражения  :  #Добавление  instanceVariableNames:  'left right'  classVariableNames:  ''  package:  'Wikipedia' .Класс сложения  >>слева:  a  справа:  b  "Метод класса для построения экземпляра класса сложения"  ^  self  new  слева:  a ;  справа:  b ;  сами .Добавление >>left:  anExpression  "Установщик для left"  left  :=  anExpression .Дополнение >>right:  anExpression  "Установщик для right"  right  :=  anExpression .Addition >>putOn:  aStream  "Объект Addition знает, как печатать себя"  aStream  nextPut:  $( .  left  putOn:  aStream .  aStream  nextPut:  $+ .  right  putOn:  aStream .  aStream  nextPut:  $) .Подкласс объекта  :  #Program  instanceVariableNames:  ''  classVariableNames:  ''  package:  'Wikipedia' .Программа >> main  |  expression  stream  |  expression  :=  Сложение  слева: ( Сложение  слева: ( Литерал  с:  1 ) справа: ( Литерал  с:  2 )) справа: ( Литерал  с:  3 ) .  stream  :=  ExpressionPrinter  on: ( Строка  new:  100 ) .  stream  write:  expression .  Transcript  show:  содержимое потока  . Transcript flush .  


Идти

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

тип Интерфейс посетителя { visitWheel ( колесо Колесо ) строка visitEngine ( двигатель Двигатель ) строка visitBody ( тело Тело ) строка visitCar ( автомобиль Автомобиль ) строка }           

Пример Java

Следующий пример написан на языке Java и показывает, как можно распечатать содержимое дерева узлов (в данном случае описывающих компоненты автомобиля). Вместо создания printметодов для каждого подкласса узла ( Wheel, Engine, Body, и Car), один класс посетителя ( CarElementPrintVisitor) выполняет требуемое действие печати. ​​Поскольку для разных подклассов узлов требуются немного разные действия для правильной печати, CarElementPrintVisitorотправляет действия на основе класса аргумента, переданного его visitметоду. CarElementDoVisitor, который аналогичен операции сохранения для другого формата файла, делает то же самое.

Диаграмма

Источники

импорт java.util.List ; интерфейс  CarElement { void accept ( CarElementVisitor посетитель ); }    интерфейс  CarElementVisitor { void visit ( Кузов кузов ) ; void visit ( Автомобиль автомобиль ); void visit ( Двигатель двигатель ); void visit ( Колесо колесо ); }             класс  Колесо реализует CarElement { private final String name ;        public Wheel ( final String name ) { this.name = name ; }         public String getName () { return name ; }       @Override public void accept ( CarElementVisitor visitor ) { /*  * accept(CarElementVisitor) в Wheel реализует  * accept(CarElementVisitor) в CarElement, поэтому вызов  * accept привязан во время выполнения. Это можно считать  * *первой* отправкой. Однако решение о вызове  * visit(Wheel) (в отличие от visit(Engine) и т. д.) может быть  * принято во время компиляции, так как во время компиляции * известно, что 'this'  является Wheel. Более того, каждая реализация  * CarElementVisitor реализует visit(Wheel), что является  * еще одним решением, принимаемым во время выполнения. Это можно  * считать *второй* отправкой.  */ visitor . visit ( this ); } }        класс  Body реализует CarElement { @Override public void accept ( CarElementVisitor visitor ) { visitor . visit ( this ); } }           class  Engine реализует CarElement { @Override public void accept ( CarElementVisitor visitor ) { visitor . visit ( this ); } }           класс  Car реализует CarElement { private final Список элементов < CarElement > ;        public Car () { this . elements = List . of ( new Wheel ( "переднее левое" ), new Wheel ( "переднее правое" ), new Wheel ( "заднее левое" ), new Wheel ( "заднее правое" ), new Body (), new Engine () ); }                    @Override public void accept ( CarElementVisitor visitor ) { for ( CarElement element : elements ) { element.accept ( visitor ) ; } visitor.visit ( this ) ; } }               class  CarElementDoVisitor implements CarElementVisitor { @Override public void visit ( Body body ) { System . out . println ( "Перемещение моего тела" ); }            @Override public void visit ( Car car ) { System.out.println ( " Завожу машину " ) ; }        @Override public void visit ( Wheel wheel ) { System.out.println ( " Пинаю свой " + wheel.getName ( ) + " wheel " ) ; }            @Override public void visit ( Engine engine ) { System . out . println ( "Запускаю двигатель" ); } }       class  CarElementPrintVisitor реализует CarElementVisitor { @Override public void visit ( Body body ) { System . out . println ( "Посещение body" ); }            @Override public void visit ( Car car ) { System.out.println ( " Посещение car " ) ; }        @Override public void visit ( Engine engine ) { System.out.println ( " Посещение engine " ) ; }        @Override public void visit ( Wheel wheel ) { System.out.println ( " Посещение " + wheel.getName ( ) + " wheel " ) ; } }           public class VisitorDemo { public static void main ( final String [] args ) { Car car = new Car ();                автомобиль . принять ( новый CarElementPrintVisitor ()); автомобиль . принять ( новый CarElementDoVisitor ()); } }    


Выход

Посещение переднего левого колесаПосещение переднего правого колесаПосещение заднего левого колесаПосещение заднего правого колесаПосещение телаВизит двигателяВизитная машинаПинаю переднее левое колесоПинаю переднее правое колесоПинаю заднее левое колесоПинаю заднее правое колесоДвигаю теломЗапускаю двигательЗавожу машину

Пример Common Lisp

Источники

( defclass auto () (( элементы : initarg : elements )))     ( defclass auto-part () (( name :initarg :name :initform "<unnamed-car-part>" )))       ( defmethod print-object ( ( p auto-part ) stream ) ( print-object ( slot-value p 'name ) stream ))         ( defclass колесо ( auto-part ) ())   ( defclass body ( auto-part ) ())   ( двигатель defclass ( автозапчасть ) ())   ( defgeneric traverse ( function object other-object ))    ( defmethod traverse ( function ( a auto ) other-object ) ( with-slots ( elements ) a ( dolist ( e elements ) ( funcall function e other-object ))))               ;; посещения с целью что-то сделать;; перехватить все ( defmethod do-something ( object other-object ) ( format t "не знаю, как ~s и ~s должны взаимодействовать~%" object other-object ))        ;; посещение с участием колеса и целого числа ( defmethod do-something (( object wheel ) ( other-object integer )) ( format t "пинание колеса ~s ~s раз~%" object other-object ))          ;; посещение с участием колеса и символа ( defmethod do-something (( object wheel ) ( other-object symbol )) ( format t "пинание колеса ~s символически с использованием символа ~s~%" object other-object ))          ( defmethod do-something (( object engine ) ( other-object integer )) ( format t "запуск двигателя ~s ~s раз~%" object other-object ))          ( defmethod do-something (( object engine ) ( other-object symbol )) ( format t "запуск engine ~s символически с использованием символа ~s~%" object other-object ))          ( let (( a ( make-instance 'auto :elements ` ( , ( make-instance 'wheel :name "front-left-wheel" ) , ( make-instance 'wheel :name "front-right-wheel" ) , ( make-instance 'wheel :name "rear-left-wheel" ) , ( make-instance 'wheel :name "rear-right-wheel" ) , ( make-instance 'body :name "body" ) , ( make-instance 'engine :name "engine" ))))) ;; переход к выводу элементов ;; поток *standard-output* здесь играет роль другого объекта ( traverse #' print a *standard-output* )                                   ( terpri ) ;; распечатать новую строку  ;; обход с произвольным контекстом из другого объекта ( обход #' do-something a 42 )     ;; обход с произвольным контекстом из другого объекта ( обход #' do-something a 'abc ))    

Выход

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

Примечания

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

( defmethod traverse ( function ( a auto )) ;; other-object removed ( with-slots ( elements ) a ( dolist ( e elements ) ( funcall function e )))) ;; отсюда тоже                ;; ... ;; альтернативный способ print-traverse ( traverse ( lambda ( o ) ( print o *standard-output* )) a )        ;; альтернативный способ сделать что-то с ;; элементами a и целым числом 42 ( traverse ( lambda ( o ) ( do-something o 42 )) a )        

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

Пример на Python

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

Источники

""" Пример шаблона посетителя. """из  abc  импорт  ABCMeta ,  абстрактный методNOT_IMPLEMENTED  =  "Вам следует это реализовать."класс  CarElement ( метакласс = ABCMeta ):  @abstractmethod  def  accept ( self ,  visitor ):  raise  NotImplementedError ( NOT_IMPLEMENTED )класс  Body ( CarElement ):  def  accept ( self ,  visitor ):  visitor . visitBody ( self )класс  Двигатель ( CarElement ):  def  accept ( self ,  visitor ):  посетитель . visitEngine ( self )class  Wheel ( CarElement ):  def  __init__ ( self ,  name ):  self . name  =  name  def  accept ( self ,  visitor ):  visitor . visitWheel ( self )class  Car ( CarElement ):  def  __init__ ( self ):  self . elements  =  [  Wheel ( "переднее левое" ),  Wheel ( "переднее правое" ),  Wheel ( "заднее левое" ),  Wheel ( "заднее правое" ),  Body (),  Engine ()  ] def  accept ( self ,  visitor ):  для  элемента  in  self . elements :  element . accept ( visitor )  visitor . visitCar ( self )класс  CarElementVisitor ( метакласс = ABCMeta ):  @abstractmethod  def  visitBody ( self ,  element ) :  вызвать  NotImplementedError ( NOT_IMPLEMENTED )  @abstractmethod  def  visitEngine ( self ,  element ):  вызвать  NotImplementedError ( NOT_IMPLEMENTED )  @abstractmethod  def  visitWheel ( self ,  element ):  вызвать  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 ( "Пинаю колесо {} ." . format ( wheel . name ))  def  visitEngine ( self ,  engine ):  print ( "Завожу двигатель." )class  CarElementPrintVisitor ( CarElementVisitor ):  def  visitBody ( self ,  body ):  print ( "Посещение body." )  def  visitCar ( self ,  car ):  print ( "Посещение car." )  def  visitWheel ( self ,  wheel ):  print ( "Посещение {} wheel." . format ( wheel . name ))  def  visitEngine ( self ,  engine ):  print ( "Посещение engine." )автомобиль  =  Автомобиль () автомобиль . принять ( CarElementPrintVisitor ()) автомобиль . принять ( CarElementDoVisitor ())

Выход

Посещение переднего левого колеса. Посещение переднего правого колеса. Посещение заднего левого колеса. Посещение заднего правого колеса. Посещение тела. Посещение двигателя. Посещение автомобиля. Пинаю свое переднее левое колесо. Пинаю свое переднее правое колесо. Пинаю свое заднее левое колесо. Пинаю свое заднее правое колесо. Движу своим телом. Завожу свой двигатель. Завожу свою машину.

Абстракция

Использование Python 3 или выше позволяет реализовать общую реализацию метода accept:

класс  Visitable :  def  accept ( self ,  visitor ):  lookup  =  "visit_"  +  self .__ qualname__ . replace ( "." ,  "_" )  return  getattr ( visitor ,  lookup )( self )

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

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

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

Ссылки

  1. ^ ab Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides (1994). Шаблоны проектирования: элементы повторно используемого объектно-ориентированного программного обеспечения . Addison Wesley. стр. 331 и далее. ISBN 0-201-63361-2.{{cite book}}: CS1 maint: несколько имен: список авторов ( ссылка )
  2. ^ Реальный пример шаблона посетителя
  3. ^ abcd Бадд, Тимоти (1997). Введение в объектно-ориентированное программирование (2-е изд.). Reading, Mass.: Addison-Wesley. ISBN 0-201-82419-1. OCLC  34788238.
  4. ^ "Шаблон дизайна Посетитель - Структура и сотрудничество". w3sDesign.com . Получено 2017-08-12 .
  5. ^ Reddy, Martin (2011). API design for C++. Бостон: Morgan Kaufmann. ISBN 978-0-12-385004-1. OCLC  704559821.

Внешние ссылки