Я продолжаю видеть ссылки на шаблон посетителей в блогах, но я должен признать, я просто не понимаю. Я прочитал статью в Википедии, посвященную шаблону, и понимаю его механику, но все еще не понимаю, когда мне его использовать.
Как человек, который совсем недавно действительно получил шаблон декоратора и теперь видит его применение абсолютно везде, я бы хотел по-настоящему интуитивно понять этот, казалось бы, удобный шаблон.
design-patterns
visitor-pattern
Джордж Мауэр
источник
источник
Ответы:
Я не очень знаком с моделью посетителя. Посмотрим, правильно ли я понял. Предположим, у вас есть иерархия животных
(Предположим, это сложная иерархия с устоявшимся интерфейсом.)
Теперь мы хотим добавить новую операцию в иерархию, а именно мы хотим, чтобы каждое животное воспроизводило свой звук. Поскольку иерархия так проста, вы можете сделать это с прямым полиморфизмом:
Но, действуя таким образом, каждый раз, когда вы хотите добавить операцию, вы должны изменить интерфейс для каждого отдельного класса иерархии. Теперь предположим, что вы удовлетворены оригинальным интерфейсом и хотите внести в него как можно меньше модификаций.
Шаблон Visitor позволяет перемещать каждую новую операцию в подходящий класс, и вам необходимо расширить интерфейс иерархии только один раз. Давай сделаем это. Сначала мы определяем абстрактную операцию (класс «Посетитель» в GoF ), который имеет метод для каждого класса в иерархии:
Затем мы модифицируем иерархию для принятия новых операций:
Наконец, мы реализуем фактическую операцию, не изменяя ни Cat, ни Dog :
Теперь у вас есть возможность добавлять операции без изменения иерархии. Вот как это работает:
источник
letsDo(Operation *v)
нужен указательtheSound.hereIsACat(c)
сделал бы работу, как вы оправдываете все накладные расходы, вносимые шаблоном? двойная диспетчеризация является оправданием.Причиной вашего замешательства, вероятно, является то, что Посетитель - смертельный неправильный человек. Многие (выдающиеся 1 !) Программисты наткнулись на эту проблему. Что он на самом деле делает, так это реализует двойную диспетчеризацию в языках, которые не поддерживают его изначально (большинство из них этого не делают).
1) Мой любимый пример - Скотт Мейерс, известный автор «Эффективного C ++», который назвал это одним из своих самых важных C ++, ага! моменты когда-либо .
источник
switch
:switch
жесткие коды принятия решений на стороне клиента (код дублирование) и не обеспечивает статическую проверку типов ( проверка на полноту и отчетливость дел и т. д.). Шаблон посетителя проверяется средством проверки типов и обычно упрощает код клиента.virtual
подобные функции так полезны в современных языках программирования - они являются основным строительным блоком расширяемых программ - на мой взгляд, способ c (вложенный переключатель или сопоставление с шаблоном и т. Д. В зависимости от выбранного языка) Гораздо чище в коде, который не должен быть расширяемым, и я был приятно удивлен, увидев этот стиль в таком сложном программном обеспечении, как Prover 9. Более важно, что любой язык, который хочет обеспечить расширяемость, вероятно, должен приспосабливаться к лучшим шаблонам диспетчеризации, чем рекурсивная одиночная диспетчеризация (т.е. посетитель).Все здесь правы, но я думаю, что это не касается "когда". Во-первых, из Design Patterns:
Теперь давайте подумаем о простой иерархии классов. У меня есть классы 1, 2, 3 и 4 и методы A, B, C и D. Выложите их, как в электронной таблице: классы - это строки, а методы - столбцы.
Теперь объектно-ориентированное проектирование предполагает, что у вас больше шансов вырастить новые классы, чем новые методы, поэтому проще добавить больше строк, так сказать. Вы просто добавляете новый класс, указываете, что отличается в этом классе, и наследует остальные.
Иногда, однако, классы относительно статичны, но вам нужно часто добавлять больше методов - добавление столбцов. Стандартный способ разработки ОО состоит в добавлении таких методов ко всем классам, что может быть дорогостоящим. Шаблон Visitor делает это легко.
Кстати, именно эту проблему намеревается решить шаблон Scala.
источник
Посетители шаблон дизайна работает очень хорошо для «рекурсивных» структур , таких как дерева каталогов, XML - структуры, или очертания документов.
Объект Visitor посещает каждый узел в рекурсивной структуре: каждый каталог, каждый тег XML, что угодно. Объект Visitor не проходит через структуру. Вместо этого методы Visitor применяются к каждому узлу структуры.
Вот типичная рекурсивная структура узлов. Может быть каталогом или тегом XML. [Если вы Java-человек, представьте себе множество дополнительных методов для создания и поддержки списка детей.]
visit
Метод применяется объект посетителей к каждому узлу в структуре. В данном случае это посетитель сверху вниз. Вы можете изменить структуруvisit
метода, чтобы сделать восходящий или другой порядок.Вот суперкласс для посетителей. Используется
visit
методом. Он «достигает» каждого узла в структуре. Посколькуvisit
метод вызываетup
иdown
, посетитель может отслеживать глубину.Подкласс может выполнять такие вещи, как подсчет узлов на каждом уровне и накапливать список узлов, генерируя хорошие пути иерархических номеров разделов.
Вот приложение. Он строит древовидную структуру
someTree
. Это создаетVisitor
,dumpNodes
.Затем он применяет
dumpNodes
к дереву.dumpNode
Объект будет «посетить» каждый узел в дереве.visit
Алгоритм TreeNode гарантирует, что каждый TreeNode используется в качестве аргумента дляarrivedAt
метода посетителя .источник
Один из способов взглянуть на это состоит в том, что шаблон посетителя - это способ позволить вашим клиентам добавлять дополнительные методы ко всем вашим классам в определенной иерархии классов.
Это полезно, когда у вас достаточно стабильная иерархия классов, но у вас есть изменяющиеся требования к тому, что нужно делать с этой иерархией.
Классический пример для компиляторов и тому подобное. Абстрактное синтаксическое дерево (AST) может точно определить структуру языка программирования, но операции, которые вы можете выполнять в AST, будут меняться по мере продвижения вашего проекта: генераторы кода, симпатичные принтеры, отладчики, анализ показателей сложности.
Без шаблона посетителя каждый раз, когда разработчик хотел добавить новую функцию, он должен был бы добавить этот метод к каждой функции в базовом классе. Это особенно сложно, когда базовые классы появляются в отдельной библиотеке или создаются отдельной командой.
(Я слышал, что он утверждал, что шаблон Visitor вступает в противоречие с хорошими практиками ОО, поскольку он перемещает операции данных от данных. Шаблон посетителя полезен именно в ситуации, когда обычные практики ОО терпят неудачу.)
источник
Существует по крайней мере три веские причины для использования шаблона посетителя:
Уменьшите распространение кода, который лишь немного отличается при изменении структуры данных.
Примените одно и то же вычисление к нескольким структурам данных, не изменяя код, который реализует вычисление.
Добавить информацию в устаревшие библиотеки без изменения устаревшего кода.
Пожалуйста, взгляните на статью, которую я написал об этом .
источник
Как уже отмечал Конрад Рудольф, он подходит для случаев, когда нам нужна двойная отправка
Вот пример, чтобы показать ситуацию, когда нам нужна двойная отправка и как посетитель помогает нам в этом.
Пример :
Допустим, у меня есть 3 типа мобильных устройств - iPhone, Android, Windows Mobile.
На всех этих трех устройствах установлено радио Bluetooth.
Предположим, что радио «синий зуб» может быть от двух отдельных OEM-производителей - Intel и Broadcom.
Просто для того, чтобы сделать пример релевантным для нашего обсуждения, давайте также предположим, что API-интерфейсы, предоставляемые радио Intel, отличаются от тех, которые представлены радио Broadcom.
Вот так выглядят мои занятия -
Теперь я хотел бы ввести операцию - Включение Bluetooth на мобильном устройстве.
Его функция подписи должна выглядеть примерно так:
Таким образом, в зависимости от правильного типа устройства и в зависимости от правильного типа радиомодуля Bluetooth , его можно включить, вызвав соответствующие шаги или алгоритм .
В принципе, это становится матрицей 3 x 2, где я пытаюсь векторизовать правильную операцию в зависимости от правильного типа вовлеченных объектов.
Полиморфное поведение в зависимости от типа обоих аргументов.
Теперь шаблон Visitor может быть применен к этой проблеме. Вдохновение приходит со страницы в Википедии: «По сути, посетитель позволяет добавлять новые виртуальные функции в семейство классов, не изменяя сами классы; вместо этого создается класс посетителя, который реализует все соответствующие специализации виртуальной функции. Посетитель принимает ссылку на экземпляр в качестве входных данных и реализует цель посредством двойной отправки ».
Двойная диспетчеризация здесь необходима благодаря матрице 3х2
Вот как будет выглядеть установка -
Я написал пример, чтобы ответить на другой вопрос, код и его объяснение упоминаются здесь .
источник
Я нашел это проще в следующих ссылках:
В http://www.remondo.net/visitor-pattern-example-csharp/ я нашел пример, который показывает фиктивный пример, который показывает, что является преимуществом шаблона посетителя. Здесь у вас есть различные классы контейнеров для
Pill
:Как вы видите выше, у вас
BilsterPack
есть пары таблеток, поэтому вам нужно умножить количество пар на 2. Также вы можете заметить, чтоBottle
используетсяunit
другой тип данных, и его нужно разыграть.Таким образом, в основном методе вы можете рассчитать количество таблеток, используя следующий код:
Обратите внимание, что приведенный выше код нарушает
Single Responsibility Principle
. Это означает, что вы должны изменить основной код метода, если добавляете новый тип контейнера. Также делать переключение дольше - плохая практика.Итак, введя следующий код:
Вы переместили ответственность за подсчет числа
Pill
s на вызываемый классPillCountVisitor
(и мы удалили оператор switch case). Это означает, что всякий раз, когда вам нужно добавить новый тип контейнера для таблеток, вы должны изменить толькоPillCountVisitor
класс. ТакжеIVisitor
интерфейс уведомления является общим для использования в других сценариях.Добавив метод Accept в класс контейнера для таблеток:
мы разрешаем посетителю посещать классы контейнеров для таблеток.
В конце мы рассчитываем количество таблеток, используя следующий код:
Это означает, что: каждый контейнер с таблетками позволяет
PillCountVisitor
посетителю увидеть количество таблеток. Он умеет считать твои таблетки.На это
visitor.Count
имеет значение таблетки.В http://butunclebob.com/ArticleS.UncleBob.IuseVisitor вы видите реальный сценарий, в котором вы не можете использовать полиморфизм (ответ), чтобы следовать принципу единой ответственности. На самом деле в:
этот
reportQtdHoursAndPay
метод предназначен для отчетности и представления, и это нарушает принцип единой ответственности. Так что лучше использовать шаблон посетителя, чтобы преодолеть проблему.источник
Двойная отправка - это всего лишь одна из причин использовать этот шаблон .
Но обратите внимание, что это единственный способ реализовать двойную или более диспетчеризацию в языках, использующих одну парадигму диспетчеризации.
Вот причины для использования шаблона:
1) Мы хотим определять новые операции, не меняя модель каждый раз, потому что модель не меняется часто, а операции часто меняются.
2) Мы не хотим связывать модель и поведение, потому что мы хотим иметь многократно используемую модель в нескольких приложениях или мы хотим иметь расширяемую модель, которая позволяет клиентским классам определять свое поведение со своими собственными классами.
3) У нас есть общие операции, которые зависят от конкретного типа модели, но мы не хотим реализовывать логику в каждом подклассе, поскольку это взорвало бы общую логику в нескольких классах и, таким образом, в нескольких местах .
4) Мы используем проектирование модели предметной области, и классы моделей той же иерархии выполняют слишком много разных вещей, которые можно собрать где-то еще .
5) Нам нужна двойная отправка .
У нас есть переменные, объявленные с типами интерфейса, и мы хотим иметь возможность обрабатывать их в соответствии с их типом среды выполнения… конечно, без использования
if (myObj instanceof Foo) {}
или каких-либо хитростей.Идея состоит, например, в том, чтобы передать эти переменные в методы, которые объявляют конкретный тип интерфейса в качестве параметра для применения определенной обработки. Этот способ не возможен из коробки, так как языки основаны на единой диспетчеризации, потому что выбранный, вызванный во время выполнения, зависит только от типа получателя во время выполнения.
Обратите внимание, что в Java вызываемый метод (подпись) выбирается во время компиляции и зависит от объявленного типа параметров, а не от их типа во время выполнения.
Последний пункт, который является причиной использования посетителя, также является следствием того, что при реализации посетителя (конечно, для языков, которые не поддерживают множественную диспетчеризацию), вам обязательно нужно внедрить реализацию с двойной диспетчеризацией.
Обратите внимание, что обход элементов (итерация) для применения посетителя к каждому из них не является причиной для использования шаблона.
Вы используете шаблон, потому что вы разделяете модель и обработку.
И используя шаблон, вы получаете дополнительную выгоду от способности итератора.
Эта способность очень мощная и выходит за рамки итерации по общему типу с определенным методом, как
accept()
и общий метод.Это особый случай использования. Поэтому я отложу это в сторону.
Пример на Java
Я проиллюстрирую добавленную стоимость паттерна на примере шахмат, где мы хотели бы определить обработку, когда игрок запрашивает перемещение фигуры.
Без использования шаблона посетителя мы могли бы определить поведение перемещения элементов непосредственно в подклассах элементов.
Мы могли бы иметь, например, такой
Piece
интерфейс:Каждый подкласс Piece будет реализовывать это, например:
И то же самое для всех подклассов Piece.
Вот класс диаграммы, который иллюстрирует этот дизайн:
Этот подход имеет три важных недостатка:
- поведение, такое как
performMove()
илиcomputeIfKingCheck()
очень вероятно, будет использовать общую логику.Например, какой бы бетон ни был
Piece
,performMove()
он, наконец, установит текущую фигуру в определенном месте и потенциально заберет фигуру противника.Разделение связанных поведений на несколько классов вместо того, чтобы собирать их, каким-то образом побеждает единую схему ответственности. Делать их ремонтопригоднее сложнее.
- обработка
checkMoveValidity()
не должна быть чем-то, чтоPiece
подклассы могут видеть или изменять.Это проверка, которая выходит за рамки действий человека или компьютера. Эта проверка выполняется при каждом действии, запрошенном игроком, чтобы убедиться, что запрошенный ход фигуры действителен.
Поэтому мы даже не хотим предоставлять это в
Piece
интерфейсе.- В сложных шахматных играх для разработчиков ботов, как правило, приложение предоставляет стандартный API (
Piece
интерфейсы, подклассы, Board, общее поведение и т. Д.) И позволяет разработчикам обогащать свою стратегию ботов.Чтобы сделать это, мы должны предложить модель, в которой данные и поведение не связаны в
Piece
реализациях.Итак, давайте использовать шаблон посетителя!
У нас есть два вида структуры:
- модельные классы, которые принимают к посещению (штуки)
- посетители, которые их посещают (двигательные операции)
Вот диаграмма классов, которая иллюстрирует шаблон:
В верхней части у нас есть посетители, а в нижней части - классы моделей.
Вот
PieceMovingVisitor
интерфейс (поведение, указанное для каждого видаPiece
):Часть определена сейчас:
Его ключевой метод:
Это обеспечивает первую отправку: вызов, основанный на
Piece
получателе.Во время компиляции метод привязан к
accept()
методу интерфейса Piece, а во время выполнения ограниченный метод будет вызван вPiece
классе времени выполнения .И именно
accept()
реализация метода будет выполнять вторую диспетчеризацию.Действительно, каждый
Piece
подкласс, который хочет посетитьPieceMovingVisitor
объект, вызываетPieceMovingVisitor.visit()
метод, передавая сам аргумент.Таким образом, компилятор ограничивает, как только время компиляции, тип объявленного параметра конкретным типом.
Есть вторая отправка.
Вот
Bishop
подкласс, который иллюстрирует это:А вот пример использования:
Недостатки посетителя
Шаблон Visitor является очень мощным шаблоном, но он также имеет некоторые важные ограничения, которые следует учитывать перед его использованием.
1) Риск уменьшить / сломать инкапсуляцию
В некоторых видах операций шаблон посетителя может уменьшить или нарушить инкапсуляцию объектов домена.
Например, так как
MovePerformingVisitor
класс должен установить координаты фактической части,Piece
интерфейс должен предоставить способ сделать это:Ответственность за
Piece
изменение координат теперь открыта для других классов, кромеPiece
подклассов.Перемещение обработки, выполняемой посетителем в
Piece
подклассы, также не вариант.Это действительно создаст другую проблему, так как
Piece.accept()
принимает любую реализацию посетителя. Он не знает, что выполняет посетитель, и поэтому не знает, нужно ли и как изменить состояние фигуры.Способ идентифицировать посетителя - выполнить постобработку в
Piece.accept()
соответствии с реализацией посетителя. Было бы очень плохая идея , поскольку это позволит создать высокое сцепление между реализациями Публичные и штучных подклассов и , кроме того, вероятно , потребуется использовать трюкgetClass()
,instanceof
или любой маркер , идентифицирующий реализацию посетителей.2) Требование изменить модель
В отличие от некоторых других шаблонов поведения,
Decorator
например, шаблон посетителей является навязчивым.Нам действительно нужно изменить начальный класс получателя, чтобы обеспечить
accept()
метод для принятия к посещению.У нас не было проблем с
Piece
подклассами, так как это наши классы .Во встроенных или сторонних классах все не так просто.
Нам нужно обернуть или унаследовать (если мы можем) их, чтобы добавить
accept()
метод.3) Направления
Шаблон создает множественные косвенные указания.
Двойная отправка означает два вызова вместо одного:
И у нас могут быть дополнительные косвенные указания, поскольку посетитель изменяет состояние посещаемого объекта.
Это может выглядеть как цикл:
источник
Кей Хорстманн имеет прекрасный пример того, где можно применить Visitor в своей книге по дизайну и шаблонам . Он суммирует проблему:
Причина, по которой это нелегко, заключается в том, что операции добавляются в сами классы структуры. Например, представьте, что у вас есть файловая система:
Вот некоторые операции (функциональные возможности), которые мы могли бы реализовать с помощью этой структуры:
Вы можете добавить функции к каждому классу в FileSystem для реализации операций (и люди делали это в прошлом, так как совершенно очевидно, как это сделать). Проблема в том, что всякий раз, когда вы добавляете новую функциональность (строка «и т. Д.» Выше), вам может понадобиться добавлять все больше и больше методов в классы структуры. В какой-то момент, после некоторого количества операций, которые вы добавили в свое программное обеспечение, методы в этих классах больше не имеют смысла с точки зрения функциональной сплоченности классов. Например, у вас есть
FileNode
методcalculateFileColorForFunctionABC()
, который реализует новейшие функции визуализации в файловой системе.Шаблон посетителя (как и многие шаблоны проектирования) возник из-за боли и страданий разработчиков, которые знали, что существует лучший способ изменить свой код, не требуя большого количества изменений повсюду, а также соблюдая принципы хорошего дизайна (высокая согласованность, низкая связь). ). По моему мнению, трудно понять полезность многих моделей, пока вы не почувствуете эту боль. Объяснение боли (как мы пытаемся сделать выше с помощью добавляемых функций и т. Д.) Занимает место в объяснении и отвлекает. По этой причине сложно понять закономерности.
Посетитель позволяет нам отделить функциональные возможности структуры данных (например,
FileSystemNodes
) от самих структур данных. Шаблон позволяет дизайну соблюдать согласованность - классы структуры данных проще (у них меньше методов), а также функциональные возможности заключены вVisitor
реализации. Это делается с помощью двойной диспетчеризации (которая является сложной частью шаблона): использованиеaccept()
методов в классах структуры иvisitX()
методов в классах Visitor (функциональности):Эта структура позволяет нам добавлять новые функциональные возможности, которые работают над структурой как конкретные посетители (без изменения классов структуры).
Например, a,
PrintNameVisitor
который реализует функциональность листинга каталога, и a,PrintSizeVisitor
который реализует версию с размером. Мы могли бы вообразить, что однажды у нас будет «ExportXMLVisitor», который генерирует данные в XML, или другой посетитель, который генерирует их в JSON, и т. Д. У нас может даже быть посетитель, который отображает мое дерево каталогов с использованием графического языка, такого как DOT , для визуализации. с другой программой.В заключение: сложность Visitor с его двойной диспетчеризацией означает, что его сложнее понять, кодировать и отлаживать. Короче говоря, он имеет высокий гик-фактор и повторяет принцип KISS. В опросе, проведенном исследователями, было показано, что Visitor является противоречивой моделью (не было единого мнения о его полезности). Некоторые эксперименты даже показали, что он не облегчает поддержку кода.
источник
На мой взгляд, объем работ по добавлению новой операции более или менее одинаков с использованием
Visitor Pattern
или прямым изменением структуры каждого элемента. Кроме того, если я добавлю новый класс элементов, скажемCow
, это повлияет на интерфейс Operation, и это распространится на все существующие классы элементов, поэтому потребуется перекомпиляция всех классов элементов. Так в чем же смысл?источник
rootElement.visit (node) -> node.collapse()
. С посетителем каждый узел реализует обход графа для всех своих потомков, так что все готово.levelsRemaining
счетчик в качестве параметра. Уменьшите его, прежде чем вызывать детей следующего уровня. Внутри вашего посетителяif(levelsRemaining == 0) return
.Шаблон посетителя как та же подземная реализация для программирования Аспектного Объекта.
Например, если вы определяете новую операцию без изменения классов элементов, с которыми она работает
источник
Краткое описание шаблона посетителя.Все классы, которые требуют модификации, должны реализовывать метод accept. Клиенты вызывают этот метод accept для выполнения некоторых новых действий с этим семейством классов, расширяя тем самым их функциональность. Клиенты могут использовать этот метод accept для выполнения широкого спектра новых действий, передавая разные классы посетителей для каждого конкретного действия. Класс посетителя содержит несколько переопределенных методов посещения, определяющих, как выполнить одно и то же конкретное действие для каждого класса в семье. Эти методы посещения получают экземпляр для работы.
Когда вы могли бы рассмотреть возможность его использования
источник
Я не понимал эту модель, пока не наткнулся на статью дяди Боба и не прочитал комментарии. Рассмотрим следующий код:
Хотя это может выглядеть хорошо, так как подтверждает единственную ответственность, оно нарушает Open / Closed принцип . Каждый раз, когда у вас есть новый тип сотрудника, вы должны будете добавить, если с проверкой типа. И если вы этого не сделаете, вы никогда не узнаете об этом во время компиляции.
С помощью шаблона посетителя вы можете сделать свой код чище, так как он не нарушает принцип открытия / закрытия и не нарушает Единую ответственность. И если вы забудете реализовать визит, он не скомпилируется:
Волшебство в том, что, хотя
v.Visit(this)
выглядит одинаково, на самом деле оно отличается, поскольку вызывает разные перегрузки посетителя.источник
Основано на превосходном ответе @Federico A. Ramponi.
Просто представьте, что у вас есть эта иерархия:
Что произойдет, если вам нужно добавить метод «Walk» здесь? Это будет болезненно для всего дизайна.
В то же время добавление метода «Прогулка» порождает новые вопросы. А как насчет "Ешь" или "Спи"? Должны ли мы действительно добавлять новый метод в иерархию Animal для каждого нового действия или операции, которые мы хотим добавить? Это ужасно и очень важно, мы никогда не сможем закрыть интерфейс Animal. Таким образом, с помощью шаблона посетителя мы можем добавить новый метод в иерархию без изменения иерархии!
Итак, просто проверьте и запустите этот пример C #:
источник
Dog
, а такжеCat
. Вы могли бы сделать их в базовом классе, чтобы они были унаследованы, или выбрать подходящий пример.посетитель
Структура посетителей:
Используйте шаблон Visitor, если:
Несмотря на то, что шаблон Visitor обеспечивает гибкость добавления новой операции без изменения существующего кода в Object, эта гибкость имеет недостаток.
Если был добавлен новый объект Visitable, он требует изменения кода в классах Visitor & ConcreteVisitor . Для решения этой проблемы существует обходной путь: используйте рефлексию, которая повлияет на производительность.
Фрагмент кода:
Объяснение:
Visitable
(Element
) является интерфейсом, и этот метод интерфейса должен быть добавлен к набору классов.Visitor
интерфейс, который содержит методы для выполнения операций надVisitable
элементамиGameVisitor
это класс, который реализуетVisitor
интерфейс (ConcreteVisitor
).Visitable
элемент принимаетVisitor
и вызывает соответствующий методVisitor
интерфейса.Game
как кElement
конкретным играм, так и кChess,Checkers and Ludo
какConcreteElements
.В приведенном выше примере
Chess, Checkers and Ludo
представлены три разные игры (иVisitable
классы). В один прекрасный день я столкнулся со сценарием для записи статистики каждой игры. Таким образом, не изменяя отдельный класс для реализации функциональности статистики, вы можете централизовать эту ответственность вGameVisitor
классе, что поможет вам без изменения структуры каждой игры.вывод:
Ссылаться на
статья о дизайне
sourcemaking статья
Больше подробностей
декоратор
Похожие сообщения:
Декоратор Pattern для IO
Когда использовать шаблон декоратора?
источник
Мне очень нравится описание и пример из http://python-3-patterns-idioms-test.readthedocs.io/en/latest/Visitor.html .
источник
Хотя я понял, как и когда, я никогда не понимал, почему. В случае, если это помогает кому-то с опытом в языке, подобном C ++, вы должны прочитать это очень внимательно.
Для ленивых мы используем шаблон посетителей, потому что «хотя виртуальные функции отправляются динамически в C ++, перегрузка функций выполняется статически» .
Или, другими словами, чтобы убедиться, что CollideWith (ApolloSpacecraft &) вызывается при передаче ссылки на SpaceShip, которая фактически привязана к объекту ApolloSpacecraft.
источник
Спасибо за потрясающее объяснение @Federico A. Ramponi , я только что сделал это в java- версии. Надеюсь, это может быть полезно.
Также, как указал @Konrad Rudolph , на самом деле это двойная диспетчеризация, использующая два конкретных экземпляра вместе для определения методов времени выполнения.
Так что на самом деле нет необходимости создавать общий интерфейс для исполнителя операций, если у нас правильно определен интерфейс операций .
Как вы ожидаете, общий интерфейс принесет нам больше ясности, хотя на самом деле он не является важной частью этого шаблона.
источник
Ваш вопрос, когда знать:
я не первый код с шаблоном посетителя. я кодирую стандарт и жду, когда возникнет необходимость, а затем рефакторинг. Допустим, у вас есть несколько платежных систем, которые вы установили по одной за раз. Во время оформления заказа у вас может быть много условий if (или instanceOf), например:
Теперь представьте, что у меня было 10 способов оплаты, это выглядит ужасно. Поэтому, когда вы видите, что посетитель приходит, чтобы разделить все это, вы в конечном итоге вызываете что-то вроде этого:
Вы можете увидеть, как реализовать это из числа примеров здесь, я просто показываю вам сценарий использования.
источник