Шаблон посетителя
Эта статья нуждается в дополнительных цитатах для проверки . ( январь 2022 г. ) |
Шаблон посетителя — это шаблон проектирования программного обеспечения , который отделяет алгоритм от структуры объекта . Благодаря такому разделению новые операции можно добавлять к существующим структурам объектов без их изменения. Это один из способов следовать принципу открытости/закрытости в объектно-ориентированном программировании и разработке программного обеспечения .
По сути, посетитель позволяет добавлять новые виртуальные функции в семейство классов без изменения классов. Вместо этого создается класс посетителя, реализующий все соответствующие специализации виртуальной функции. Посетитель принимает ссылку на экземпляр в качестве входных данных и реализует цель посредством двойной отправки .
Языки программирования с типами сумм и сопоставлением с образцом устраняют многие преимущества шаблона посетителя, поскольку класс посетителя может как легко разветвляться по типу объекта, так и генерировать ошибку компилятора, если определен новый тип объекта, что и делает посетитель. еще не справляюсь.
Обзор
[ редактировать ]Посетитель [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 диаграмме классов выше 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()
).
Диаграмма классов
[ редактировать ]Подробности
[ редактировать ]Шаблон посетителя требует языка программирования , поддерживающего единую отправку , как это делают распространенные объектно-ориентированные языки (такие как 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
, аналогичный операции сохранения файла в другом формате, делает то же самое.
Диаграмма
[ редактировать ]Источники
[ редактировать ]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 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
Общий пример Лиспа
[ редактировать ]Источники
[ редактировать ](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 times kicking wheel "front-right-wheel" 42 times kicking wheel "rear-left-wheel" 42 times kicking wheel "rear-right-wheel" 42 times don't know how "body" and 42 should interact starting engine "engine" 42 times kicking wheel "front-left-wheel" symbolically using symbol ABC kicking wheel "front-right-wheel" symbolically using symbol ABC kicking wheel "rear-left-wheel" symbolically using symbol ABC kicking wheel "rear-right-wheel" symbolically using symbol ABC don't know how "body" and ABC should interact starting 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, abstractmethod
NOT_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)
Можно расширить это, чтобы перебирать порядок разрешения методов класса, если они хотят вернуться к уже реализованным классам. Они также могут использовать функцию перехвата подкласса, чтобы заранее определить поиск.
Связанные шаблоны проектирования
[ редактировать ]- Шаблон итератора – определяет принцип обхода, такой как шаблон посетителя, без дифференциации типов внутри проходимых объектов.
- Кодирование Чёрча - родственная концепция функционального программирования, в которой тегированные типы объединения/суммы могут моделироваться с использованием поведения «посетителей» таких типов и которая позволяет шаблону посетителя эмулировать варианты и шаблоны .
См. также
[ редактировать ]Ссылки
[ редактировать ]- ^ Jump up to: а б Эрих Гамма, Ричард Хелм, Ральф Джонсон, Джон Влиссидес (1994). Шаблоны проектирования: элементы объектно-ориентированного программного обеспечения многократного использования . Эддисон Уэсли. стр. 331 и далее . ISBN 0-201-63361-2 .
{{cite book}}
: CS1 maint: несколько имен: список авторов ( ссылка ) - ^ Реальный пример шаблона посетителя
- ^ Jump up to: а б с д Бадд, Тимоти (1997). Введение в объектно-ориентированное программирование (2-е изд.). Ридинг, Массачусетс: Аддисон-Уэсли. ISBN 0-201-82419-1 . OCLC 34788238 .
- ^ «Шаблон проектирования «Посетитель» — структура и сотрудничество» . w3sDesign.com . Проверено 12 августа 2017 г.
- ^ Редди, Мартин (2011). Разработка API для C++ . Бостон: Морган Кауфманн. ISBN 978-0-12-385004-1 . OCLC 704559821 .
Внешние ссылки
[ редактировать ]- Семейство шаблонов проектирования посетителей в Wayback Machine (архивировано 22 октября 2015 г.). Дополнительные архивы: 12 апреля 2004 г. , 5 марта 2002 г. Грубая глава из книги «Принципы, шаблоны и практики гибкой разработки программного обеспечения» , Роберт К. Мартин , Прентис Холл.
- Шаблон посетителя в UML и LePUS3 (язык описания дизайна)
- Статья « Компонентизация: пример посетителя» , Бертран Мейер и Карин Арно, Компьютер (IEEE), том 39, № 7, июль 2006 г., страницы 23-30.
- Статья Теоретико-типовая реконструкция шаблона посетителя
- Статья « Сущность шаблона посетителя » Йенса Палсберга и К. Барри Джея . 1997 года Документ IEEE-CS COMPSAC , показывающий, что методы Accept() не нужны, когда доступно отражение; вводит термин «Обход» для этой техники.
- Статья « Время для размышлений Брюса Уоллеса » с подзаголовком «Возможности отражения Java 1.2 устраняют обременительные методы Accept() из вашего шаблона Посетитель»
- Шаблон посетителя с использованием отражения (java).
- Проект с открытым исходным кодом PerfectJPattern . Обеспечивает контекстно-свободную и типобезопасную реализацию шаблона посетителя в Java на основе делегатов.
- Шаблон дизайна посетителей