Программирование >>  Разработка устойчивых систем 

1 ... 165 166 167 [ 168 ] 169 170 171 ... 196


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

Но справедливости ради предположим, что у вас имеется фиксированная основная иерархия классов; возможно, вы получили ее от другого разработчика и не можете вносить в нее изменения. Если бы у вас был исходный код библиотеки, в базовый класс можно было добавить новые виртуальные функции, но по каким-то причинам это невозможно. В другом, более правдоподобном сценарии добавление новых функций создает неудобства, уродливо выглядит или по иным причинам затрудняет сопровождение. БЧ возражает: распределение всех этих операций по узловым классам усложняет понимание, сопровождение и модификацию системы - но как вы вскоре убедитесь, паттерн Посетитель может породить еще больще проблем. Другой аргумент БЧ - интерфейс основной иерархии нежелательно захламлять слишком большим количеством операций (с другой стороны, если интерфейс становится излишне тяжеловесным , возникает вопрос, не перегружен ли объект лишней функциональностью).

Впрочем, создатели библиотеки предвидели, что вы захотите добавить в иерархию новые операции, поэтому они включили функцию visit().

Итак, возникает следующая дилемма: вы хотите добавить в базовый класс новые функции, но по каким-то причинам не можете изменять его. Как обойти это препятствие?

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

: C10:BeeAnclFlowers.cpp Паттерн Посетитель, linclude <algorithm> linclude <iostream> linclude <string> linclude <vector> linclude <ctime> linclude <cstdlib> linclude ../purge.h using namespace std:

class Gladiolus: class Renuculus: class Chrysanthemum:

class Visitor { public:

virtual void visit(Gladiolus* f) = 0: virtual void visit(Renuculus* f) = 0:



class Chrysanthemum : public Flower { public:

virtual void accept(Visitors v) { V.visit(this):

Новая возможность - получение строки: class StringVal : public Visitor {

string s: public:

operator const string&O { return s: } virtual void visit(Gladiolus*) { s = Gladiolus :

virtual void visit(Renuculus*) { s = Renuculus :

virtual void visit(Chrysanthemum*) { s = Chrysanthemum :

Новая возможность - выполнение операций Bee

class Bee : public Visitor {

public:

virtual void visit(Gladiolus*) { cout Bee and Gladiolus endl:

virtual void visit(Renuculus*) { cout Bee and Renuculus endl:

virtual void visit(Chrysanthemum*) { cout Bee and Chrysanthemum endl:

virtual void visit(Chrysanthemum* f) = 0: virtual -VisitorO {}

class Flower { public:

virtual void accept(Visitor&) = 0: virtual -FlowerO {}

class Gladiolus : public Flower { public:

virtual void accept(Visitor& v) { V.visit(this):

class Renuculus : public Flower { public:

virtual void accept(Visitor& v) { v.visit(this):



case 0 case 1 case 2

return new Gladiolus: return new Renuculus; return new Chrysanthemum:

int mainO {

srand(time(0)): Раскрутка генератора случайных чисел vector<Flower*> v(10): generateCv.beginO. v.endO. FlowerGenO); vector<Flower*>::iterator it;

Выглядит почти так же. как если бы в класс Flower была добавлена виртуальная функция для получения строкового представления: StringVal sval:

for(it = V.beginO: it != v.endO; it++) { (*it)->accept(sval); cout string(sval) endl;

Выполнение операций Bee со всеми объектами Flower: Bee bee;

forCit = V.beginO; it != v.endO; it++)

(*it)->accept(bee): purge(v); } /;-

Класс Flower возглавляет основную иерархию, и каждый из его подтипов умеет принимать Посетителей (Visitor) функцией accept(). Иерархия flower() не содержит других операций, помимо accept(), поэтому вся функциональность иерархии Flower содержится в иерархии Visitor. Обратите внимание: классы Visitor должны обладать информацией о конкретных разновидностях Flower, и при добавлении нового типа Flower приходится перерабатывать всю иерархию Visitor.

Функция accept() в подклассах Flower начинает двойную диспетчеризацию. В ходе первой диспетчеризации определяется точный тип Flower, в ходе второй - точный тип Visitor. Зная оба типа, можно выполнять операции, соответствующие обоим типам.

Скорее всего, вам не придется использовать Посетителя - мотивация для его применения необычна, а жесткие ограничения сводят его ценность на нет . Примеры в книге БЧ неубедительны; сначала авторы рассматривают компилятор (написанием компиляторов занимается не так уж много людей, и вряд ли в них будет использоваться этот паттерн), а потом извиняются за остальные примеры и говорят, что не стали бы применять Посетителя в подобных случаях. Чтобы отказаться от обычной объектно-ориентированной структуры в пользу Посетителя, понадобятся куда более убедительные доводы - какие преимущества вы реально получите в обмен на радикальное усложнение и ограниченность? Почему нельзя просто добавить новые виртуальные функции в базовый класс, когда выяснится, что они вам нужны? А если уж действительно нужно включить новые функции

struct FlowerGen { Flower* operatorOO { switchCrandO % 3) { default:



1 ... 165 166 167 [ 168 ] 169 170 171 ... 196

© 2006 - 2024 pmbk.ru. Генерация страницы: 0
При копировании материалов приветствуются ссылки.
Яндекс.Метрика