Jump to content

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

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

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

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

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

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

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

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

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

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

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

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

Определение

[ редактировать ]

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

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

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

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

[ редактировать ]

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

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

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

Приложение

[ редактировать ]

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

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

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

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

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

[ редактировать ]

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

Структура

[ редактировать ]

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

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

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

Диаграмма UML последовательности показывает взаимодействие во время выполнения: 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). [5] : 381 
Посетитель в LePUS3 ( легенда )

Подробности

[ редактировать ]

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

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

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

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

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

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

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

пример С#

[ редактировать ]

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

using System;namespace Wikipedia;public interface Visitor{    void Visit(Literal literal);      void Visit(Addition addition);}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 double Value { get; set; }    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 Expression Left { get; set; }    public Expression Right { get; set; }    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)    {        // Emulate 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

[ редактировать ]

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

"There's no syntax for creating a class. Classes are created by sending messages to other classes."WriteStream subclass: #ExpressionPrinter    instanceVariableNames: ''    classVariableNames: ''    package: 'Wikipedia'.ExpressionPrinter>>write: anObject    "Delegates the action to the object. The object doesn't need to be of any special    class; it only needs to be able to understand the message #putOn:"    anObject putOn: self.    ^ anObject.Object subclass: #Expression    instanceVariableNames: ''    classVariableNames: ''    package: 'Wikipedia'.Expression subclass: #Literal    instanceVariableNames: 'value'    classVariableNames: ''    package: 'Wikipedia'.Literal class>>with: aValue    "Class method for building an instance of the Literal class"    ^ self new        value: aValue;        yourself.Literal>>value: aValue  "Setter for value"  value := aValue.Literal>>putOn: aStream    "A Literal object knows how to print itself"    aStream nextPutAll: value asString.Expression subclass: #Addition    instanceVariableNames: 'left right'    classVariableNames: ''    package: 'Wikipedia'.Addition class>>left: a right: b    "Class method for building an instance of the Addition class"    ^ self new        left: a;        right: b;        yourself.Addition>>left: anExpression    "Setter for left"    left := anExpression.Addition>>right: anExpression    "Setter for right"    right := anExpression.Addition>>putOn: aStream    "An Addition object knows how to print itself"    aStream nextPut: $(.    left putOn: aStream.    aStream nextPut: $+.    right putOn: aStream.    aStream nextPut: $).Object subclass: #Program    instanceVariableNames: ''    classVariableNames: ''    package: 'Wikipedia'.Program>>main    | expression stream |    expression := Addition                    left: (Addition                            left: (Literal with: 1)                            right: (Literal with: 2))                    right: (Literal with: 3).    stream := ExpressionPrinter on: (String new: 100).    stream write: expression.    Transcript show: stream contents.    Transcript flush.


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

type Visitor interface {	visitWheel(wheel Wheel) string	visitEngine(engine Engine) string	visitBody(body Body) string	visitCar(car Car) string}

Пример Java

[ редактировать ]

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

Диаграмма

[ редактировать ]

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

Источники

[ редактировать ]
import java.util.List;interface CarElement {    void accept(CarElementVisitor visitor);}interface CarElementVisitor {    void visit(Body body);    void visit(Car car);    void visit(Engine engine);    void visit(Wheel wheel);}class Wheel implements 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) in Wheel implements       * accept(CarElementVisitor) in CarElement, so the call       * to accept is bound at run time. This can be considered       * the *first* dispatch. However, the decision to call       * visit(Wheel) (as opposed to visit(Engine) etc.) can be       * made during compile time since 'this' is known at compile       * time to be a Wheel. Moreover, each implementation of       * CarElementVisitor implements the visit(Wheel), which is       * another decision that is made at run time. This can be       * considered the *second* dispatch.       */      visitor.visit(this);  }}class Body implements CarElement {  @Override  public void accept(CarElementVisitor visitor) {      visitor.visit(this);  }}class Engine implements CarElement {  @Override  public void accept(CarElementVisitor visitor) {      visitor.visit(this);  }}class Car implements CarElement {    private final List<CarElement> elements;    public Car() {        this.elements = List.of(            new Wheel("front left"), new Wheel("front right"),            new Wheel("back left"), new Wheel("back right"),            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("Moving my body");    }    @Override    public void visit(Car car) {        System.out.println("Starting my car");    }    @Override    public void visit(Wheel wheel) {        System.out.println("Kicking my " + wheel.getName() + " wheel");    }    @Override    public void visit(Engine engine) {        System.out.println("Starting my engine");    }}class CarElementPrintVisitor implements CarElementVisitor {    @Override    public void visit(Body body) {        System.out.println("Visiting body");    }    @Override    public void visit(Car car) {        System.out.println("Visiting car");    }    @Override    public void visit(Engine engine) {        System.out.println("Visiting engine");    }    @Override    public void visit(Wheel wheel) {        System.out.println("Visiting " + wheel.getName() + " wheel");    }}public class VisitorDemo {    public static void main(final String[] args) {        Car car = new Car();        car.accept(new CarElementPrintVisitor());        car.accept(new CarElementDoVisitor());    }}


Visiting front left wheelVisiting front right wheelVisiting back left wheelVisiting back right wheelVisiting bodyVisiting engineVisiting carKicking my front left wheelKicking my front right wheelKicking my back left wheelKicking my back right wheelMoving my bodyStarting my engineStarting my car

Общий пример Лиспа

[ редактировать ]

Источники

[ редактировать ]
(defclass auto ()  ((elements :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 wheel (auto-part) ())(defclass body (auto-part) ())(defclass engine (auto-part) ())(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))));; do-something visitations;; catch all(defmethod do-something (object other-object)  (format t "don't know how ~s and ~s should interact~%" object other-object));; visitation involving wheel and integer(defmethod do-something ((object wheel) (other-object integer))  (format t "kicking wheel ~s ~s times~%" object other-object));; visitation involving wheel and symbol(defmethod do-something ((object wheel) (other-object symbol))  (format t "kicking wheel ~s symbolically using symbol ~s~%" object other-object))(defmethod do-something ((object engine) (other-object integer))  (format t "starting engine ~s ~s times~%" object other-object))(defmethod do-something ((object engine) (other-object symbol))  (format t "starting engine ~s symbolically using symbol ~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")))))  ;; traverse to print elements  ;; stream *standard-output* plays the role of other-object here  (traverse #'print a *standard-output*)  (terpri) ;; print newline  ;; traverse with arbitrary context from other object  (traverse #'do-something a 42)  ;; traverse with arbitrary context from other object  (traverse #'do-something a 'abc))
"front-left-wheel""front-right-wheel""rear-left-wheel""rear-right-wheel""body""engine"kicking wheel "front-left-wheel" 42 timeskicking wheel "front-right-wheel" 42 timeskicking wheel "rear-left-wheel" 42 timeskicking wheel "rear-right-wheel" 42 timesdon't know how "body" and 42 should interactstarting engine "engine" 42 timeskicking wheel "front-left-wheel" symbolically using symbol ABCkicking wheel "front-right-wheel" symbolically using symbol ABCkicking wheel "rear-left-wheel" symbolically using symbol ABCkicking wheel "rear-right-wheel" symbolically using symbol ABCdon't know how "body" and ABC should interactstarting engine "engine" symbolically using symbol ABC

Примечания

[ редактировать ]

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

(defmethod traverse (function (a auto)) ;; other-object removed  (with-slots (elements) a    (dolist (e elements)      (funcall function e)))) ;; from here too  ;; ...  ;; alternative way to print-traverse  (traverse (lambda (o) (print o *standard-output*)) a)  ;; alternative way to do-something with  ;; elements of a and integer 42  (traverse (lambda (o) (do-something o 42)) a)

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

Пример Python

[ редактировать ]

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

Источники

[ редактировать ]
"""Visitor pattern example."""from abc import ABCMeta, abstractmethodNOT_IMPLEMENTED = "You should implement this."class CarElement(metaclass=ABCMeta):    @abstractmethod    def accept(self, visitor):        raise NotImplementedError(NOT_IMPLEMENTED)class Body(CarElement):    def accept(self, visitor):        visitor.visitBody(self)class Engine(CarElement):    def accept(self, visitor):        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("front left"), Wheel("front right"),            Wheel("back left"), Wheel("back right"),            Body(), Engine()        ]    def accept(self, visitor):        for element in self.elements:            element.accept(visitor)        visitor.visitCar(self)class CarElementVisitor(metaclass=ABCMeta):    @abstractmethod    def visitBody(self, element):        raise NotImplementedError(NOT_IMPLEMENTED)    @abstractmethod    def visitEngine(self, element):        raise NotImplementedError(NOT_IMPLEMENTED)    @abstractmethod    def visitWheel(self, element):        raise NotImplementedError(NOT_IMPLEMENTED)    @abstractmethod    def visitCar(self, element):        raise NotImplementedError(NOT_IMPLEMENTED)class CarElementDoVisitor(CarElementVisitor):    def visitBody(self, body):        print("Moving my body.")    def visitCar(self, car):        print("Starting my car.")    def visitWheel(self, wheel):        print("Kicking my {} wheel.".format(wheel.name))    def visitEngine(self, engine):        print("Starting my engine.")class CarElementPrintVisitor(CarElementVisitor):    def visitBody(self, body):        print("Visiting body.")    def visitCar(self, car):        print("Visiting car.")    def visitWheel(self, wheel):        print("Visiting {} wheel.".format(wheel.name))    def visitEngine(self, engine):        print("Visiting engine.")car = Car()car.accept(CarElementPrintVisitor())car.accept(CarElementDoVisitor())
Visiting front left wheel.Visiting front right wheel.Visiting back left wheel.Visiting back right wheel.Visiting body.Visiting engine.Visiting car.Kicking my front left wheel.Kicking my front right wheel.Kicking my back left wheel.Kicking my back right wheel.Moving my body.Starting my engine.Starting my car.

Абстракция

[ редактировать ]

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

class Visitable:    def accept(self, visitor):        lookup = "visit_" + self.__qualname__.replace(".", "_")        return getattr(visitor, lookup)(self)

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

[ редактировать ]

См. также

[ редактировать ]
  1. ^ Jump up to: а б Эрих Гамма, Ричард Хелм, Ральф Джонсон, Джон Влиссидес (1994). Шаблоны проектирования: элементы объектно-ориентированного программного обеспечения многократного использования . Эддисон Уэсли. стр. 331 и далее . ISBN  0-201-63361-2 . {{cite book}}: CS1 maint: несколько имен: список авторов ( ссылка )
  2. ^ Реальный пример шаблона посетителя
  3. ^ Jump up to: а б с д Бадд, Тимоти (1997). Введение в объектно-ориентированное программирование (2-е изд.). Ридинг, Массачусетс: Аддисон-Уэсли. ISBN  0-201-82419-1 . OCLC   34788238 .
  4. ^ «Шаблон проектирования «Посетитель» — структура и сотрудничество» . w3sDesign.com . Проверено 12 августа 2017 г.
  5. ^ Редди, Мартин (2011). Разработка API для C++ . Бостон: Морган Кауфманн. ISBN  978-0-12-385004-1 . OCLC   704559821 .
[ редактировать ]
Arc.Ask3.Ru: конец переведенного документа.
Arc.Ask3.Ru
Номер скриншота №: ab2564f83174ab2451edf5ab81bf37d4__1721646660
URL1:https://arc.ask3.ru/arc/aa/ab/d4/ab2564f83174ab2451edf5ab81bf37d4.html
Заголовок, (Title) документа по адресу, URL1:
Visitor pattern - Wikipedia
Данный printscreen веб страницы (снимок веб страницы, скриншот веб страницы), визуально-программная копия документа расположенного по адресу URL1 и сохраненная в файл, имеет: квалифицированную, усовершенствованную (подтверждены: метки времени, валидность сертификата), открепленную ЭЦП (приложена к данному файлу), что может быть использовано для подтверждения содержания и факта существования документа в этот момент времени. Права на данный скриншот принадлежат администрации Ask3.ru, использование в качестве доказательства только с письменного разрешения правообладателя скриншота. Администрация Ask3.ru не несет ответственности за информацию размещенную на данном скриншоте. Права на прочие зарегистрированные элементы любого права, изображенные на снимках принадлежат их владельцам. Качество перевода предоставляется как есть. Любые претензии, иски не могут быть предъявлены. Если вы не согласны с любым пунктом перечисленным выше, вы не можете использовать данный сайт и информация размещенную на нем (сайте/странице), немедленно покиньте данный сайт. В случае нарушения любого пункта перечисленного выше, штраф 55! (Пятьдесят пять факториал, Денежную единицу (имеющую самостоятельную стоимость) можете выбрать самостоятельно, выплаичвается товарами в течение 7 дней с момента нарушения.)