Посетитель (Visitor)
реализации: java, количество: 9
реализации(исходники)
+добавить
Посетитель - паттерн поведения объектов, задающий стратегии обхода. оригинал codelab.ru источник codelab.ru
Условия, Задача, Назначение
Описывает операцию, которая должна быть выполнена над каждым объектом из некоторой произвольной структуры.
Позволяет определить и задать на выполнение новую операцию, не изменяя ни классы этих объектов, ни их структуры. оригинал codelab.ru источник codelab.ru
Мотивация
Рассмотрим компилятор, который представляет программу в виде абстрактного синтаксического дерева. Над такими деревьями он должен выполнять операции «статического семантического» анализа, например, проверять, что все переменные определены. Еще ему нужно генерировать код. Аналогично можно было бы определить операции контроля типов, оптимизации кода, анализа потока выполнения, проверки того, что каждой переменной было присвоено конкретное значение перед первым использованием, и т.д. Более того, абстрактные синтаксические деревья могли бы служить для красивой печати программы, реструктурирования кода и вычисления различных метрик программы. codelab.ru оригинал codelab.ru источник
codelab.ru оригинал codelab.ru источник
источник codelab.ru codelab.ru оригинал
оригинал codelab.ru codelab.ru источник
Признаки применения, использования паттерна Посетитель (Visitor)
Используйте паттерн посетитель, когда: источник codelab.ru оригинал codelab.ru- В структуре присутствуют объекты многих классов
С различными интерфейсами и вы хотите выполнять над ними операции, зависящие от конкретных классов. codelab.ru codelab.ru оригинал источник - Над объектами, входящими в состав структуры, надо выполнять разнообразные, не связанные между собой операции и вы не хотите «засорять» классы такими операциями. Посетитель позволяет объединить родственные операции, поместив их в один класс. Если структура объектов является общей для нескольких приложений, то паттерн посетитель позволит в каждое приложение включить только относящиеся к нему операции. codelab.ru codelab.ru оригинал источник
- Классы, устанавливающие структуру объектов, изменяются редко, но новые операции над этой структурой добавляются часто.
При изменении классов, представленных в структуре, нужно будет переопределить интерфейсы всех посетителей, а это может вызвать затруднения. Поэтому если классы меняются достаточно часто, то, вероятно, лучше определить операции прямо в них. оригинал codelab.ru источник codelab.ru
Решение
источник codelab.ru codelab.ru оригинал
источник codelab.ru оригинал codelab.ru
Участники паттерна Посетитель (Visitor)
codelab.ru источник оригинал codelab.ru- Visitor (NodeVisitor) – посетитель.
Объявляет операцию VisitXXX для каждого класса ConcreteElement в структуре объектов. Имя и сигнатура этой операции идентифицируют класс, который посылает посетителю запрос Visit-а. Имя метода позволяет посетителю определить, элемент какого конкретного класса он посещает, а ссылка на этот элемент передающая при вызове – обращаться к нему через его интерфейс. источник codelab.ru codelab.ru оригинал - ConcreteVisitor (TypeCheckingVisitor) - конкретный посетитель.
Реализует все операции, объявленные в классе Visitor. Каждая операция реализует фрагмент алгоритма, определенного только для класса соответствующего объекта в структуре. Класс ConcreteVisitor предоставляет контекст для этого алгоритма и сохраняет его локальное состояние. Часто в этом состоянии аккумулируются результаты, полученные в процессе обхода структуры. codelab.ru оригинал codelab.ru источник - Element (Node) – элемент.
Определяет операцию Accept, которая принимает посетителя в качестве аргумента. источник оригинал codelab.ru codelab.ru - ConcreteElement (AssignmentNode, VariableRefNode) – конкретный элемент.
Реализует операцию Accept, принимающую посетителя как аргумент. Реализует он ее каждый раз по-разному: а именно вызывает только тот Visit-метод, который для него предназначен. оригинал codelab.ru источник codelab.ru - ObjectStructure (Program) - структура объектов.
Перечисляет свои элементы.
Предоставляет посетителю высокоуровневый интерфейс для посещения своих элементов.
Может быть как составным объектом (см. паттерн компоновщик), так и коллекцией, например списком или множеством. источник оригинал codelab.ru codelab.ru
Схема использования паттерна Посетитель (Visitor)
Клиент, использующий паттерн посетитель, должен создать объект класса ConcreteVisitor, а затем обойти всю структуру, посетив каждый ее элемент. codelab.ru codelab.ru источник оригинал
codelab.ru источник оригинал codelab.ru
Вопросы, касающиеся реализации паттерна Посетитель (Visitor)
С каждым объектом структуры ассоциирован некий класс посетителя Visitor. источник оригинал codelab.ru codelab.ru
class Visitor { public: virtual void VisitElementA(ElementA*); virtual void VisitElementB(Elements*); // и так далее для остальных конкретных элементов protected: Visitor() ; };
class Element { public: virtual ~Element(); virtual void Accept(Visitors) = 0; protected: Element(); }; class ElementA : public Element { public: ElementA(); virtual void Accept(Visitors v) { v.VisitElementA(this); } }; class ElementB : public Element { public: ElementB(); virtual void Accept(Visitors v) { v.VisitElementB(this); } };
class CompositeElement : public Element { public: virtual void Accept(Visitors); private: List<Element*>* _children; }; void CompositeElement::Accept (Visitors v) { ListIterator<Element*> i(_children); for (i.First(); !i.IsDone(); i.Next() { i .Currentltem()->Accept(v) ; } v.VisitCompositeElement(this); }
package visitor.ex; public interface Visitor { void VisitElementA(ElementA el); void VisitElementB(ElementB el); void VisitCompositeElement(CompositeElement el); }
package visitor.ex; void Accept(Visitor v); } public void Accept(Visitor v) { v.VisitElementA(this); } } public void Accept(Visitor v) { v.VisitElementB(this); } }
package visitor.ex; private List<Element> children; public void Accept(Visitor v) { Iterator<Element> childIter = children.iterator(); while (childIter.hasNext()) { childIter.next().Accept(v); } v.VisitCompositeElement(this); } }
- Двойная диспетчеризация.
По своей сути паттерн посетитель позволяет, не изменяя классы, добавлять в них новые операции. Достигает он этого с помощью приема, называемого двойной диспетчеризацией. Данная техника хорошо известна. Некоторые языки программирования (например, CLOS) поддерживают ее явно. Языки же вроде C++ и Smalltalk поддерживают только одинарную диспетчеризацию.
Для определения того, какая операция будет выполнять запрос, в языках с одинарной диспетчеризацией неоходимы имя запроса и тип получателя. Например, то, какая операция будет вызвана для обработки запроса GenerateCode, зависит от типа объекта в узле, которому адресован запрос. В C++ вызов GenerateCode для экземпляра VariableRefNode приводит к вызову функции VariableRefNode::GenerateCode (генерирующей код обращения к переменной). Вызов же GenerateCode для узла класса AssignmentNode приводит к вызову функции AssignmentNode::GenerateCode (генерирующей код для оператора присваивания). Таким образом, выполняемая операция определяется одновременно видом запроса и типом получателя.
Понятие «двойная диспетчеризация» означает, что выполняемая операция зависит от вида запроса и типов двух получателей. Accept – это операция с двойной диспетчеризацией. Ее семантика зависит от типов двух объектов: Visitor и Element. Двойная диспетчеризация позволяет посетителю запрашивать разные операции для каждого класса элемента (Если есть двойная диспетчеризация, то почему бы не быть тройной, четверной или диспетчеризации произвольной кратности? Двойная диспетчеризация - это просто частный случай множественной диспетчеризации, при которой выбираемая операция зависит от любого числа типов. CLOS как раз и поддерживает множественную диспетчеризацию.В языках с поддержкой двойной или множественной диспетчеризации необходимость в паттерне посетитель возникает гораздо реже.)
Поэтому возникает необходимость в паттерне посетитель: выполняемая операция зависит и от типа посетителя, и от типа посещаемого элемента. Вместо статической привязки операций к интерфейсу класса Element мы можем консолидировать эти операции в классе Visitor и использовать Accept для привязки их во время выполнения. Расширение интерфейса класса Element сводится к определению нового подкласса Visitor, а не к модификации многих подклассов Element. codelab.ru codelab.ru оригинал источник - Какой участник несет ответственность за обход структуры.
Посетитель должен обойти каждый элемент структуры объектов. Вопрос в том, как туда попасть. Ответственность за обход можно возложить либо на саму структуру объектов, либо на посетителя либо на отдельный объект-итератор (см. паттерн итератор).
Чаще всего структура объектов отвечает за обход. Коллекция просто обходит все свои элементы, вызывая для каждого операцию Accept. Составной объект обычно обходит самого себя, «заставляя» операцию Accept посетить потомков текущего элемента и рекурсивно вызвать Accept для каждого из них.
Другое решение - воспользоваться итератором для посещения элементов.
Можно применить внутренний или внешний итератор, в зависимости от того, что доступно и более эффективно. Поскольку внутренние итераторы реализуются самой структурой объектов, то работа с ними во многом напоминает предыдущее решение, когда за обход отвечает структура.
Основное различие заключается в том, что внутренний итератор не приводит к двойной диспетчеризации: он вызывает операцию посетителя с элементом в качестве аргумента, а не операцию элемента с посетителем в качестве аргумента. Однако использовать паттерн посетитель с внутренним итератором легко в том случае, когда операция посетителя вызывает операцию элемента без рекурсии.
Можно даже поместить алгоритм обхода в посетитель, хотя закончится это дублированием кода обхода в каждом классе ConcreteVisitor для каждого агрегата ConcreteElement. Основная причина такого решения – необходимость реализовать особо сложную стратегию обхода, зависящую от результатов операций над объектами структуры. источник оригинал codelab.ru codelab.ru
Результаты
Некоторые достоинства и недостатки паттерна посетитель: codelab.ru codelab.ru оригинал источник- Упрощает добавление новых операций.
С помощью посетителей легко добавлять операции, зависящие от компонентов сложных объектов. Для определения новой операции над структурой объектов достаточно просто ввести нового посетителя. Напротив, если функциональность распределена по нескольким классам, то для определения новой операции придется изменить каждый класс. codelab.ru оригинал codelab.ru источник - Объединяет родственные операции и отсекает те, которые не имеют к ним отношения.
Родственное поведение не разносится по всем классам, присутствующим в структуре объектов, оно локализовано в посетителе. Не связанные друг с другом функции распределяются по отдельным подклассам класса Visitor. Это способствует упрощению как классов, определяющих элементы, так и алгоритмов, инкапсулированных в посетителях. Все относящиеся к алгоритму структуры данных можно скрыть в посетителе. codelab.ru источник оригинал codelab.ru - Добавление новых классов ConcreteElement затруднено.
Паттерн посетитель усложняет добавление новых подклассов класса Element. Каждый новый конкретный элемент требует объявления новой абстрактной операции в классе Visitor, которую нужно реализовать в каждом из существующих на данный момент классов ConcreteVisitor. Иногда большинство конкретных посетителей могут унаследовать операцию по умолчанию, предоставляемую классом Visitor, но это, скорее, исключение, чем правило.
Поэтому при решении вопроса о том, стоит ли использовать паттерн посетитель, нужно прежде всего посмотреть, что будет изменяться чаще: алгоритм, применяемый к объектам структуры, или классы объектов, составляющих эту структуру. Вполне вероятно, что сопровождать иерархию классов Visitor будет нелегко, если новые классы ConcreteElement добавляются часто. В таких случаях проще определить операции прямо в классах, представленных в структуре. Если же иерархия классов Element стабильна, но постоянно расширяется набор операций или модифицируются алгоритмы, то паттерн посетитель поможет лучше управлять такими изменениями. источник codelab.ru codelab.ru оригинал - Посещение различных иерархий классов.
Итератор может посещать объекты структуры по мере ее обхода, вызывая операции объектов. Но итератор не способен работать со структурами, состоящими из объектов разных типов. Так, интерфейс класса Iterator, может всего лишь получить доступ к объектам типа Item:- template <class Item>
- class Iterator {
- // ...
- Item CurrentltemO const;
- };
Отсюда следует, что все элементы, которые итератор может посетить, должны иметь общий родительский класс Item.
У посетителя таких ограничений нет. Ему разрешено посещать объекты, не имеющие общего родительского класса. В интерфейс класса Visitor можно добавить операции для объектов любого типа. Например, в следующем объявлении:- class Visitor {
- public:
- // ...
- void VisitMyType(MyType*);
- void VisitYourType(YourType*);
- };
классы МуТуре и YourType необязательно должны быть связаны отношением наследования. источник оригинал codelab.ru codelab.ru - Аккумулирование состояния.
Посетители могут аккумулировать информацию о состоянии при посещении объектов структуры. Если не использовать этот паттерн, состояние придется передавать в виде дополнительных аргументов операций, выполняющих обход, или хранить в глобальных переменных. codelab.ru источник codelab.ru оригинал - Нарушение инкапсуляции.
Применение посетителей подразумевает, что у класса ConcreteElement достаточно развитый интерфейс для того, чтобы посетители могли справиться со своей работой. Поэтому при использовании данного паттерна приходится предоставлять много открытых операций для доступа к внутреннему состоянию элементов, что ставит под угрозу инкапсуляцию. codelab.ru оригинал источник codelab.ru
Пример
Рассмотрим пример автоматизации ассортимента товаров в магазине компьютерных комплектующих. источник оригинал codelab.ru codelab.ruДля поддержки посетителя в класс Equipment добавляется операция Accept: Equipment. codelab.ru источник оригинал codelab.ru
Известные применения паттерна Посетитель (Visitor)
В компиляторе Smalltalk-80 имеется класс посетителя, который называется ProgramNodeEnumerator. В основном он применяется в алгоритмах анализа исходного текста программы и не используется ни для генерации кода, ни для красивой печати, хотя мог бы. оригинал codelab.ru источник codelab.ruМарк Линтон (Mark Linton) ввел термин «посетитель» (Visitor) в спецификацию библиотеки для построения приложений X Consortium's Fresco Application Toolkit. codelab.ru оригинал источник codelab.ru
Родственные паттерны
Компоновщик: посетители могут использоваться для выполнения операции над всеми объектами структуры, определенной с помощью паттерна компоновщик. codelab.ru оригинал источник codelab.ruИнтерпретатор: посетитель может использоваться для выполнения интерпретации. codelab.ru codelab.ru источник оригинал
Реализации: java(9) +добавить реализацию
1) Equipment.java, code #529[автор:this]
2) CompositeEquipment.java, code #530[автор:this]
3) EquipmentVisitor.java, code #531[автор:this]
4) Card.java, code #532[автор:this]
5) FloppyDisk.java, code #533[автор:this]
6) Chassis.java, code #534[автор:this]
7) PricingVisitor.java, code #535[автор:this]
8) InventoryVisitor.java, code #536[автор:this]
9) Client.java, code #537[автор:this]



