|
Программирование >> Разработка устойчивых систем
мающую любой объект из иерархии операций. В результате вы получаете две иерархии классов вместо одной. Кроме того, основная иерархия становится очень непрочной - добавление нового класса требует внесения изменений во всей второй иерархии. В книге БЧ отмечается, что по этой причине основная иерархия должна изменяться как можно реже . Такое требование значительно ограничивает свободу действий и сокращает практическую полезность этого паттерна. Но справедливости ради предположим, что у вас имеется фиксированная основная иерархия классов; возможно, вы получили ее от другого разработчика и не можете вносить в нее изменения. Если бы у вас был исходный код библиотеки, в базовый класс можно было добавить новые виртуальные функции, но по каким-то причинам это невозможно. В другом, более правдоподобном сценарии добавление новых функций создает неудобства, уродливо выглядит или по иным причинам затрудняет сопровождение. БЧ возражает: распределение всех этих операций по узловым классам усложняет понимание, сопровождение и модификацию системы - но как вы вскоре убедитесь, паттерн Посетитель может породить еще больще проблем. Другой аргумент БЧ - интерфейс основной иерархии нежелательно захламлять слишком большим количеством операций (с другой стороны, если интерфейс становится излишне тяжеловесным , возникает вопрос, не перегружен ли объект лишней функциональностью). Впрочем, создатели библиотеки предвидели, что вы захотите добавить в иерархию новые операции, поэтому они включили функцию 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:
|
© 2006 - 2024 pmbk.ru. Генерация страницы: 0
При копировании материалов приветствуются ссылки. |