Анимация
JavaScript


Главная  Библионтека 

0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 [ 82 ] 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105

DocElement. В предыдущем примере для этого придется добавить новый класс vec-torGraphic, производный от класса DocElement. Следуйте нащим указаниям.

• Откройте файл DocElementvisitor.h и добавьте новое неполное объявление класса vectorGraphiс.

• Включите в класс DocElement новую чисто виртуальную перефуженную фушщию.

class DocElementvisitor {

... как и прежде ...

virtual void visitvectorGraphicCvectorGraphic&) = 0;

• Реализуйте в каждом конкретном инспекторе функцию visitvectorGraphic. Эта функция может быть пустой. В качестве альтернативы можно определить пустую функцию DocElementvisitor::VisitvectorGraphic, однако в этом случае компилятор не сообщит ничего, если вы забудете реализовать ее в конкретных инспекторах.

• Реализуйте функцию Accept в классе vectorGraphi с. Сделайте это обязательно. Если, применяя прямое наследование от класса DocElement, вы забудете реализовать функцию Accept, ничего сфашного не произойдет - вы получите сообщение об ошибке компиляции. Однако, если вы выполните наследование от другого класса, например, класса Graphic, в котором определена функция Accept, компилятор не проронит ни слова о том, что вы забыли сделать класс vectorGraphi с инспектируемым. Эта ошибка проявится лишь во время выполнения профаммы, когда вы с удивлением обнаружите, что ни одна реализация функции visitvectorGraphic не вызывается. Найти такую ошибку достаточно сложно.

Следствие: все иерархии классов DocElement и DocElementvisitor будут скомпилированы повторно, и вам придется добавлять довольно большое количество механического кода лишь для того, чтобы сохранить работоспособность профаммы. В некоторых случаях такое решение может оказаться соверщенно неприемлемым.

Роберт Мартин (Robert Martin, 1996) изобрел интересную разновидность шаблона visitor, в котором цикличность ликвидируется с помощью оператора dynamic cast. В рамках этого подхода для иерархии инспектора создается фиктивный базовый класс Basevisitor, выступающий в роли носителя информации. Он не имеет никакого содержания, а следовательно, совершенно независим. Функция-член Accept из инспектируемой иерархии получает ссылку на объекг класса Basevi si tor и применяет к нему оператор dy-namic cast, чтобы обнаружить соответствующий инспектор. Если соответствие достигнуто, функция Accept переходит от инспектируемой иерархии к иерархии инспектора.

Это звучит довольно загадочно, но на самом деле все очень просто. Посмофим, как можно реализовать щаблон Acyclic visitor в проекте DocElement/ DocElementvisitor. Во-первых, определим базовый класс инспектора.

class DocElementvisitor

public:

virtual void ~DocElementvisi tor() {}

Пустой виртуальный десфуктор выполняет две важные вещи. Во-первых, он предоставляет в распоряжение класса DocElementvisitor функциональные возможности механизма RTTI (mntime type information). (Оператор dynamic cast можно применять лишь к тем типам, которые содержат хотя бы одну виртуальную функцию.) Во-



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

Определение класса DocElement остается прежним. Для нас представляет интерес определение чисто виртуальной функции AcceptCDocElementvisitor&).

Затем для каждого производного класса в инспектируемой иерархии (корнем которой является класс DocElement) определим небольшой инспекГируюший класс, имеюший только одну функцию visitxxx. Например, для класса Paragraph нужно определить следующий класс.

class Paragraphvisitor {

public:

virtual void visitParagraphCParagraph&) = 0;

Реализация функции Paragraph::Accept выглядит так.

void Paragraph::Accept(visitor* v) {

if (Paragraphvisitor* p =

dynami c cast<Parag raphvi si tor*>(&v))

p->visitParagraph(*this);

else {

здесь можно вызвать функцию-ловушку

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

Аналогичные реализации функции Accept определяются во всех классах, производных от класса DocElement. В заключение конкретный инспектор вьгеодится из класса DocElе-mentvi si tor и базовых инспекторов для всех классов, представляющих интерес.

class DocStats :

public DocElementvisitor, Необходим

public Paragraphvisitor, нужен для инспектирования

объектов класса Paragraph public RasterBitmapvisitor, нужен для инспектирования

объектов класса RasterBitmap

public:

void visitParagraph(Paragraph& par)

chars += par.NumCharsO; words += par.NumwordsQ;

void visitRasterBitmap(RasterBitmap&) ++images ;



Структура классов, полученная в результате, показана на рис. 10.2. Горизонтальные пунктирные линии изображают зависимости, существующие в иерархии, а вертикальная - границу между иерархиями. Обратите внимание на то, как оператор dynami c cast позволяет волшебным образом перескакивать с одного иерархического дерева на другое, используя в качестве пружины фиктивный класс DocElementvisitor.

DocElement

+Accept(visitor: DocElementVisltor&)

Para{

jraph

Accept(vis: DocElement

Visitors)

Rasteii

Bitmap

Accept(visitor: DocElem

entVlsitor&)

DocStats

ParagraphVisitor

RasterBitmapVisitor

DocElementvisitor

Рис. 10.2. Структура классов для ациклического инспектора

Хотя описанная структура содержит в себе большое количество деталей и взаимодействий, базовая структура довольно проста. Убедимся в этом, проанализировав поток событий. Допустим, что у нас есть указатель pDocElem на объект класса DocElement, имеющего динамический ("реальный") тип Paragraph. Тогда поступим следующим образом.

DocStats stats; pDocElem->AcceptCstats);

Это повлечет за собой такие события.

1. Объект stats автоматически преобразовывается в ссылку на объект класса DocElementvisitor, поскольку он является открытым наследником этого класса.

2. Вызывается виртуальная функция Paragraph::Accept.



0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 [ 82 ] 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105