Посетитель — паттерн поведения классов.
Назначение
Описывает операцию, выполняемую с каждым объектом из некоторой структуры. Паттерн посетитель позволяет определить новую операцию, не изменяя классы этих объектов.
Visitor: мотивация
Рассмотрим компилятор, который представляет программу в виде абстрактного синтаксического дерева. Над такими деревьями он должен выполнять операции «статического семантического» анализа, например проверять, что все переменные определены. Еще ему нужно генерировать код. Аналогично можно было бы определить операции контроля типов, оптимизации кода, анализа потока выполнения, проверки того, что каждой переменной было присвоено конкретное значение перед первым использованием, и т.д. Более того, абстрактные синтаксические деревья могли бы служить для красивой печати программы, реструктурирования кода и вычисления различных метрик программы. В большинстве таких операций узлы дерева, представляющие операторы присваивания, следует рассматривать иначе, чем узлы, представляющие переменные и арифметические выражения. Поэтому один класс будет создан для операторов. присваивания, другой - для доступа к переменным, третий - для арифметических выражений и т.д. Набор классов узлов, конечно, зависит от компилируемого языка, но не очень сильно.
Рисунок 49
На представленной диаграмме показана часть иерархии классов Node. Проблема здесь в том, что если раскидать все операции по классам различных узлов, то получится система, которую трудно понять, сопровождать и изменять. Вряд ли кто-нибудь разберется в программе, если код, отвечающий за проверку типов, будет перемешан с кодом, реализующим красивую печать или анализ потока выполнения. Кроме того, добавление любой новой операции потребует перекомпиляции всех классов. Оптимальный вариант - наличие возможности добавлять операции по отдельности и отсутствие зависимости классов узлов от применяемых к ним операций.
И того, и другого можно добиться, если поместить взаимосвязанные операции из каждого класса в отдельный объект, называемый посетителем, и передавать его элементам абстрактного синтаксического дерева по мере обхода. ≪Принимая≫ посетителя, элемент посылает ему запрос, в котором содержится, в частности, класс элемента. Кроме того, в запросе присутствует в виде аргумента и сам элемент. Посетителю в данной ситуации предстоит выполнить операцию над элементом, ту самую, которая наверняка находилась бы в классе элемента.
Например, компилятор, который не использует посетителей, мог бы проверить тип процедуры, вызвав операцию TypeCheck для представляющего ее абстрактного синтаксического дерева. Каждый узел дерева должен был реализовать операцию TypeCheck путем рекурсивного вызова ее же для своих компонентов (см. приведенную выше диаграмму классов). Если же компилятор проверяет тип процедуры посредством посетителей, то ему достаточно создать объект класса TypeCheckingVisitor и вызвать для дерева операцию Accept, передав ей этот объект в качестве аргумента. Каждый узел должен был реализовать Accept путем обращения к посетителю: узел, соответствующий оператору присваивания, вызывает операцию посетителя Visit Assignment, а узел, ссылающийся на переменную, - операцию VisitVariableRef erence. To, что раньше было операцией TypeCheck в классе AssignmentNode, стало операцией VisitAssignment в классе TypeCheckingVisitor.
Чтобы посетители могли заниматься не только проверкой типов, нам необходим абстрактный класс Nodevisitor, вляющийся родителем для всех посетителей синтаксического дерева. Приложение, которому нужно вычислять метрики программы, определило бы новые подклассы Nodevisitor, так что нам не пришлось бы добавлять зависящий от приложения код в классы узлов. Паттерн посетитель инкапсулирует операции, выполняемые на каждой фазе компиляции, в классе Visitor, ассоциированном с этой фазой.
Применяя паттерн посетитель, вы определяете две иерархии классов: одну для элементов, над которыми выполняется операция (иерархия Node), а другую – для посетителей, описывающих те операции, которые выполняются над элементами (иерархия NodeVisitor). Новая операция создается путем добавления подкласса в иерархию классов посетителей. До тех пор пока грамматика языка остается постоянной (то есть не добавляются новые подклассы Node), новую функциональность можно получить путем определения новых подклассов NodeVisitor.
Рисунок 50
Visitor: применимость
Используйте паттерн посетитель, когда:
• В структуре присутствуют объекты многих классов с различными интерфейсами и вы хотите выполнять над ними операции, зависящие от конкретных классов;
• Над объектами, входящими в состав структуры, надо выполнять разнообразные, не связанные между собой операции и вы не хотите «засорять» классы такими операциями. Посетитель позволяет объединить родственные операции, поместив их в один класс. Если структура объектов является общей для нескольких приложений, то паттерн посетитель позволит в каждое приложение включить только относящиеся к нему операции;
• Классы, устанавливающие структуру объектов, изменяются редко, но новые операции над этой структурой добавляются часто. При изменении классов, представленных в структуре, нужно будет переопределить интерфейсы всех посетителей, а это может вызвать затруднения. Поэтому если классы меняются достаточно часто, то, вероятно, лучше определить операции прямо в них.
Visitor: структура
Рисунок 51
Участники
• Visitor (NodeVisitor) – посетитель - объявляет операцию Visit для каждого класса ConcreteElement в структуре объектов. Имя и сигнатура этой операции идентифицируют класс, который посылает посетителю запрос Visit. Это позволяет посетителю определить, элемент какого конкретного класса он посещает. Владея такой информацией, посетитель может обращаться к элементу напрямую через его интерфейс;
• Concrete Visitor (TypeCheckingVisitor) - конкретный посетитель - реализует все операции, объявленные в классе Visitor. Каждая операция реализует фрагмент алгоритма, определенного для класса соответствующего объекта в структуре. Класс ConcreteVisitor предоставляет контекст для этого алгоритма и сохраняет его локальное состояние. Часто в этом состоянии аккумулируются результаты, полученные в процессе обхода структуры;
• Element (Node) – элемент - определяет операцию Accept, которая принимает посетителя в качестве аргумента;
• ConcreteElement (AssignmentNode, VariableRefNode) – конкретный элемент - реализует операцию Accept, принимающую посетителя как аргумент;
• ObjectStructure (Program) - структура объектов - может перечислить свои элементы;
- может предоставить посетителю высокоуровневый интерфейс для посещения своих элементов;
- может быть как составным объектом (см. паттерн компоновщик), так и коллекцией, например списком или множеством.
Отношения
Клиент, использующий паттерн посетитель, должен создать объект класса ConcreteVisitor, азатем обойти всю структуру, посетив каждый ее элемент. При посещении элемента последний вызывает операцию посетителя, соответствующую своему классу. Элемент передает этой операции себя в качестве аргумента, чтобы посетитель мог при необходимости получить доступ к его состоянию. На представленной диаграмме взаимодействий показаны отношения между объектом, структурой, посетителем и двумя элементами.
Visitor: результаты
• Упрощает добавление новых операций. С помощью посетителей легко добавлять операции, зависящие от компонентов сложных объектов. Для определения новой операции над структурой объектов достаточно просто ввести нового посетителя. Напротив, если функциональность распределена по нескольким классам, то для определения новой операции придется изменить каждый класс;
• Объединяет родственные операции и отсекает те, которые не имеют к ним отношения. Родственное поведение не разносится по всем классам, присутствующим в структуре объектов, оно локализовано в посетителе. Не связанные друг с другом функции распределяются по отдельным подклассам класса Visitor. Это способствует упрощению как классов, определяющих элементы, так и алгоритмов, инкапсулированных в посетителях. Все относящиеся к алгоритму структуры данных можно скрыть в посетителе;
• Добавление новых классов ConcreteElement затруднено. Паттерн посетитель усложняет добавление новых подклассов класса Element. Каждый новый конкретный элемент требует объявления новой абстрактной операции в классе Visitor, которую нужно реализовать в каждом из существующих классов ConcreteVis itor. Иногда большинство конкретных посетителей могут унаследовать операцию по умолчанию, предоставляемую классом Visitor, что скорее исключение, чем правило. Поэтому при решении вопроса о том, стоит ли использовать паттерн посетитель, нужно прежде всего посмотреть, что будет изменяться чаще: алгоритм, применяемый к объектам структуры, или классы объектов, составляющих эту структуру. Вполне вероятно, что сопровождать иерархию классов Visitor будет нелегко, если новые классы ConcreteElement добавляются часто. В таких случаях проще определить операции прямо в классах, представленных в структуре. Если же иерархия классов Element стабильна, но постоянно расширяется набор операций или модифицируются алгоритмы, то паттерн посетитель поможет лучше управлять такими изменениями;
• Посещение различных иерархий классов. Итератор может посещать объекты структуры по мере ее обхода, вызывая операции объектов. Но итератор не способен работать со структурами, состоящими из объектов разных типов. У посетителя таких ограничений нет. Ему разрешено посещать объекты, не имеющие общего родительского класса. В интерфейс класса Visitor можно добавить операции для объектов любого типа.
• Аккумулирование состояния. Посетители могут аккумулировать информацию о состоянии при посещении объектов структуры. Если не использовать этот паттерн, состояние придется передавать в виде дополнительных аргументов операций, выполняющих обход, или хранить в глобальных переменных;
• Нарушение инкапсуляции. Применение посетителей подразумевает, что у класса ConcreteElement достаточно развитый интерфейс для того, чтобы посетители могли справиться со своей работой. Поэтому при использовании данного паттерна приходится предоставлять открытые операции для доступа к внутреннему состоянию элементов, что ставит под угрозу инкапсуляцию.
Системные паттерны
Паттерн Model-View-Controller(MVC).
Представьте MP3 проигрыватель, например iTunes. Интерфейс программы используется для добавления новых песен, управления списками воспроизведения и т.д. Проигрыватель ведет базу данных с названиями и информацией о песнях и воспроизводит их, причем в процессе воспроизведения пользовательский интерфейс постоянно обновляется: в нём выводится название текущей песни, позиция воспроизведения и т.д. В основе этой модели заложен паттерн Модель-Представление-Контроллер. Вы видите как обновляется информация (название и т.д.) и слышите, как она воспроизводится. Представление обновляется автоматически. Вы хотите воспроизвести песню и работаете с интерфейсом, а ваши действия передаются контроллеру. Контроллер выполняет операции с моделью, «приказывает» модели начать воспроизведение песни. Модель содержит всю информацию состояния, данные и логику приложения, необходимые для ведения базы данных и воспроизведения MP3 файлов. Модель оповещает представление об изменении состояния.
MVC : структура
1 2
|
|
5
Рисунок 52
Участники
Контроллер. Получает данные, вводимые пользователем и определяет их смысл для модели.
Модель. Хранит все данные, информацию состояния и логику приложения. Она не знает о существовании представления иконтроллера, хотя и предоставляет интерфейс для получения/изменения состояния,а также может отправлять оповещения об изменениях состояния наблюдателям.
Представление. Определяет представление модели (пользовательский интерфейс). Как правило, представление получает состояние и данные для отображения непосредственно от модели.
1 – Пользователь взаимодействует с моделью. Представление – «окно», через которое пользователь воспринимает модель. Когда вы делаете что-то с представлением, например, щелкаете по кнопке вопроизведения, представление сообщает контроллеру, какая операция была выполнена. Контроллер должен обработать это действие.
2 – Контроллер обращается к модели с запросами об изменеии состояния. Контроллер получает действия пользователя и интерпретирует их. Если вы щелкаете на кнопке, контроллер должен разобраться, что это значит и какие опреации с моделью должны быть выполнены при данном действии.
3 – Контроллер также может обратиться к представлению с запросом об изменении. Когда контролллер получает действие от представления, в результате его обработки он модет обратиться к представлению с запросом на изменение, например, заблокировать некоторые кнопки.
4 – Модель оповещает представление об изменении состояния. Когда в модели что-то изменяется, вследствие действий пользователя или других внутренних изменений, например, переходу к следующей песне в списке, модель оповещает представление об изменеии состояния.
5 – Представление запрашивает у модели информацию состояния. Представление получает отбражаемую информацию состояния непосредственно от модели. Например, когда модель оповещает представление о нпчале вопроизведения новой песни, представление запрашивает название песни и отображает его. Представление также может запросить у модели информацию состояния в результате запроса на изменение состояния со стороны контроллера.
Контроллер является наблюдателем для модели. В некоторых архитектурах контроллер регистрируется у модели и оповещается о её изменениях, например, если какие-либо аспекты модели напрямую влияют на пользовательский интерфейс (в некоторых состояних модели отдельные элементы интерфейса могут блокироваться).
Функции контроллера не сводятся к передаче данных модели. Контроллер отвечает за интерпретацию ввода и выполнение соответствующих операций с моделью. Почему нельзя сделать всё в коде представления? Можно, но нежелательно по двум причинам. Во-первых, это усложнит код представления – у него появятся две обязанности: управление пользовательским интерфейсом и логика управления моделью. Во-вторых, между представление и моделью формируется жёсткая привязка. О повторном использовании кода представления с другой моделью можно забыть. Логическая изоляция представления и контроллера способствуют формированию более гибкой и расширяемой архитектуры, которая лучше адаптируется к возможным изменениям.
Другие типы паттернов
Дата: 2019-02-25, просмотров: 240.