Паттерн Visitor (Посетитель)

Название и классификация паттерна

Посетитель — паттерн поведения объектов.

Назначение паттерна Visitor

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

Является классической техникой для восстановления потерянной информации о типе.

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

Предоставляет механизм двойной диспетчеризации.

Обсуждение паттерна Visitor

Основным назначением паттерна Visitor является введение абстрактной функциональности для совокупной иерархической структуры объектов «элемент», а именно паттерн Visitor позволяет, не изменяя классы Element, добавлять в них новые операции. Для этого вся обрабатывающая функциональность переносится из самих классов Element (эти классы становятся «легковесными») в иерархию наследования Visitor.

При этом паттерн Visitor использует технику «двойной диспетчеризации». Обычно при передаче запросов используется «одинарная диспетчеризация» — то, какая операция будет выполнена для обработки запроса, зависит от имени запроса и типа получателя. В «двойной диспетчеризации» вызываемая операция зависит от имени запроса и типов двух получателей (типа Visitor и типа посещаемого элемента Element).

Реализуется паттерн Visitor следующим образом. Создается иерархия классов Visitor, в абстрактном базовом классе которой для каждого подкласса Element совокупной структуры определяется чисто виртуальный метод visit(). Каждый метод visit() принимает один аргумент — указатель или ссылку на подкласс Element.

Каждая новая добавляемая операция моделируется при помощи конкретного подкласса Visitor. Подклассы Visitor реализуют visit() методы, объявленные в базовом классе Visitor.

Добавляем один чисто виртуальный метод accept() в базовый класс иерархии Element. В качестве параметра accept() принимает единственный аргумент — указатель или ссылку на абстрактный базовый класс иерархии Visitor.

Каждый конкретный подкласс Element реализует метод ассерЦ) следующим образом: используя полученный в качестве параметра адрес экземпляра подкласса Visitor, просто вызывает его метод visit(), передавая в качестве единственного параметра указатель this.

Теперь «элементы» и «посетители» готовы. Если клиенту нужно выполнить какую-либо операцию, то он создает экземпляр объекта соответствующего подкласса Visitor и вызывает accept() метод для каждого объекта Element, передавая экземпляр Visitor в качестве параметра.

При вызове метода accept() ищется правильный подкласс Element. Затем, при вызове метода visit() программное управление передается правильному подклассу Visitor. Таким образом, двойная диспетчеризация получается как сумма одинарных диспетчеризаций сначала в методе accept(), а затем в методе visit().

Паттерн Visitor позволяет легко добавлять новые операции — нужно просто добавить новый производный от Visitor класс. Однако паттерн Visitor следует использовать только в том случае, если подклассы Element совокупной иерархической структуры остаются стабильными (неизменяемыми). В противном случае нужно приложить значительные усилия на обновление всей иерархии Visitor.

Несмотря на то что реализации метода accept() в подклассах иерархии Element всегда одинаковая, этот метод не может быть перенесен в базовый класс Element и наследоваться производными классами. В этом случае адрес, получаемый с помощью указателя this, будет всегда соответствовать базовому типу Element.

UML-диаграмма классов паттерна Visitor

Структура паттерна Посетитель показана на рис. 77.

Участники

Visitor — Посетитель: объявляет операцию Visit для каждого класса ConcreteElement в структуре объектов. Имя и сигнатура этой one-

v->VisitConcreteElementA(this)

v->VisitConcreteElementB(this)^^

Рис. 77. UML-диаграмма паттерна Посетитель

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

Concrete Visitor — конкретный Посетитель: реализует все операции, объявленные в классе Visitor. Каждая операция реализует фрагмент алгоритма, определенного для класса соответствующего объекта в структуре. Класс ConcreteVisitor предоставляет контекст для этого алгоритма и сохраняет его локальное состояние. Часто в этом состоянии аккумулируются результаты, полученные в процессе обхода структуры.

Element — элемент: определяет операцию Accept, которая принимает Посетителя в качестве аргумента.

ConcreteElement — конкретный элемент: реализует операцию Accept, принимающую Посетителя как аргумент.

ObjectStructure — структура объектов:

  • • может перечислить свои элементы;
  • • может предоставить Посетителю высокоуровневый интерфейс для посещения своих элементов;
  • • может быть как составным объектом (см. паттерн Компоновщик), так и коллекцией, например списком или множеством.

Пример паттерна Visitor

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

Достоинства и недостатки паттерна Посетитель

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

Объединяет родственные операции и отсекает те, которые не имеют к ним отношения. Родственное поведение не разносится по всем классам, присутствующим в структуре объектов, оно локализовано в Посетителе. Не связанные друг с другом функции распределяются по отдельным подклассам класса Visitor. Это способствует упрощению как классов, определяющих элементы, так и алгоритмов, инкапсулированных в Посетителях. Все относящиеся к алгоритму структуры данных можно скрыть в Посетителе.

Добавление новых классов Concrete Element затруднено. Каждый новый конкретный элемент требует объявления новой абстрактной операции в классе Visitor, которую нужно реализовать в каждом из существующих классов ConcreteVisitor. Иногда большинство конкретных Посетителей могут унаследовать операцию по умолчанию, предоставляемую классом Visitor, что скорее исключение, чем правило. Поэтому при решении вопроса о том, стоит ли использовать паттерн Посетитель, нужно прежде всего посмотреть, что будет изменяться чаще: алгоритм, применяемый к объектам структуры, или классы объектов, составляющих эту структуру.

Реализация паттерна Visitor по шагам

Добавьте в метод accept(Visitor) иерархию «элемент».

Создайте базовый класс Visitor и определите методы visit() для каждого типа «элемента».

Создайте производные классы Visitor для каждой «операции», исполняемой над «элементами».

Клиент создает объект Visitor и передает его в вызываемый метод accept().

#include

#include using namespace std;

// 1. Добавьте в метод accept(Visitor) иерархию "элемент" class Element {

public:

virtual void accept(class Visitor &v) = 0;

};

class This: public Element

{

public:

/*virtual*/void accept(Visitor &v); string thiss()

{

return "This";

}

};

class That: public Element

{

public:

/*virtual*/void accept(Visitor &v); string that()

{

return "That";

}

};

class TheOther: public Element

{

public:

/*virtual*/void accept(Visitor &v); string theOther()

{

return "TheOther";

}

// 2. Создайте базовый класс Visitor и определите // методы visit ()для каждого типа "элемента" class Visitor {

public:

virtual void visit(This *e) = 0; virtual void visit(That *e) = 0; virtual void visit(TheOther *e) = 0;

};

/*virtual*/void This: :accept( Visitor &v)

{

v.visit(this);

}

/*virtual*/void That::accept(Visitor &v)

{

v.visit(this);

}

/*virtual*/void TheOther::accept(Visitor &v)

{

v.visit(this);

}

// 3. Создайте производные классы Visitor для каждой // "операции", исполняемой над "элементами" class UpVisitor: public Visitor {

/*virtual*/void visit(This *e)

{

cout << "do Up on" + e->thiss() << ' ';

}

/*virtual*/void visit(That *e)

{

cout << "do Up on" + e->that() << ' ';

}

/*virtual */void visit(TheOther *e)

{

cout << "do Up on" + e->theOther() << ' ';

}

};

class Down Visitor: public Visitor

{

/*virtual*/void visit(This *e)

{

cout << "do Down on " + e->thiss() << ' ';

}

/*virtual*/void visit(That *e)

{

cout << "do Down on " + e->that() << ' ';

}

/*virtual */void visit(TheOther *e)

{

cout << "do Down on " + e->theOther() << ' ';

}

};

int main()

{

Element *list[] =

{

new This(), new That(), new TheOther()

};

UpVisitor up; // 4. Клиент создает Down Visitor down; // объекты Visitor for (int i = 0; i < 3; i++)

//и передает каждый list[i]->accept(up); for (i = 0; i < 3; i++)

// в вызываемый метод accept() list[i]->accept(down);

}

Вывод программы:

do Up on This do Down on This do Up on That

do Down on That do Up on TheOther do Down on TheOther

Родственные паттерны

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

Интерпретатор: Посетитель может использоваться для выполнения интерпретации.

 
< Пред   СОДЕРЖАНИЕ     След >