Анимация
JavaScript
|
Главная Библионтека ориентированный на применение прототипов. Именно это и требуется от хорошо продуманного проекта. Возникающий контекст очень привлекателен. Клиенты, которым нужны расширенные стратегии, могут извлекать выгоды из более широких функциональных возможностей, не изменяя базовых функциональных свойств класса-владельца. Не забывайте, что пользователи (но не библиотека) определяют, какую стратегию применяет класс. В отличие от обычных множественных интерфейсов, стратегии дают пользователю возможность добавлять новые функциональные возможности главного класса, не вдаваясь в подробности его устройства. 1.7. Деструкторы классов стратегий Разрабатывая классы стратегий, следует учитывать одну важную деталь. Чаше всего при создании классов, производных от своих стратегий, главный класс использует открытый интерфейс. По этой причине пользователь может автоматически конвертировать главный класс в стратегию, а затем удалить этот указатель с помощью оператора delete. Если в классе стратегии не определен виртуальный деструктор, применение оператора delete к указателю на объект этого класса приводит к непредсказуемым последствиям.* typedef widgetManager<PrototypeCreator> MyWidgetManager; MywidgetManager wm; PrototypeCreator<widget>* pCreator = &wm; Сомнительно, но можно, delete pCreator; прекрасно компилируется, но приводит к непредсказуемым последствиям. Однако определение виртуального деструктора для стратегии противоречит ее статической природе и снижает производительность. Многие стратегии вообще не содержат данных-членов, определяя, по сути, лишь функциональные возможности. Любая виртуальная функция приводит к избыточному размеру объектов данного класса, поэтому применения виртуального конструктора следует избегать. Решение этой проблемы заключается в следующем. При выводе главного класса из классов стратегий следует использовать защищенное или закрытое наследование. Однако это может ограничить применение расширенных стратегий (раздел 1.6). Подобную задачу можно легко и эффективно решить, если потребовать, чтобы стратегии применяли невиртуальный защищенный деструктор. template <class Т> struct OpNewCreator { static т* CreateC) { return new т; protected: ~OpNewCreatorC) {} Поскольку деструктор защищен, объекты стратегий могут уничтожаться только объектами производных классов, поэтому теперь внешние классы не могут применять опе- * В главе 4 показано, почему это происходит. Глава 1. Разработка классов на основе стратегий 35 ратор delete к указателям на объекты стратегий. Деструктор не виртуален, поэтому дополнительных затрат памяти или замедления работы программы не происходит. 1.8. Факультативные возможности, предоставляемые неполной конкретизацией Язык С++ придает стратегиям особую мощь, снабжая их интересным свойством. Если функция-член шаблонного класса никогда не используется, она не конкретизируется - компилятор вообще игнорирует ее, проверяя лишь синтаксические ошибки. Это дает главным классам возможность задавать и применять факультативные свойства классов стратегий. Например, определим функцию-член Swi tchPrototype в классе WidgetManager. Код библиотеки template <template <class> class CreationPolicy> class WidgetManager : public CreationPolicy<widget> { void switchPrototype(Widget* pNewPrototype) { CreationPolicy<Widget>& myPolicy = *this; delete myPolicy.GetPrototype(); myPolicy.SetPrototype(pNewPrototype); Возникающий контекст очень интересен. • Если пользователь конкретизирует класс WidgetManager с помощью класса стратегии Creator, поддерживающей работу с прототипами, можно использовать функцию Swi tchPrototype. • Если пользователь конкретизирует класс WidgetManager с помощью класса стратегии Creator, не поддерживающей работу с прототипами, и попытается использовать функцию Swi tchPrototype, во время компиляции возникнет ошибка. • Если пользователь конкретизирует класс WidgetManager с помощью класса стратегии Creator, не поддерживающей работу с прототипами, и не пытается использовать функцию Swi tchPrototype, программа считается правильной. Все сказанное выше означает, что класс widgetManager может использовать факультативные расширенные интерфейсы, но при этом продолжает правильно работать и с интерфейсами, обладающими более бедными возможностями, пока пользователь не попытается использовать определенные функции класса WidgetManager. Автор класса WidgetManager может определить стратегию Creator следующим образом. Стратегия Creator определяет шаблонный класс типа т, содержащий функцию-член Create. Эта функция должна возвращать указатель на новый объект типа т. В реализации можно определить две дополнительные функции-члены:т* GetPrototypeC) и SetPrototype(T*), характеризующие семантику В ссютветствии со стандарто.м языка С++ строгость синтаксической проверки неиспользуемых шаблонных функций зависит от конкретной реачизации. Компилятор не выполняет семантическую проверку - например, поиск символов не проводится. получения и установки прототипов, используемых для создания объектов. В этом случае класс widgetManager содержит функцию-член Switchproto-typeCT* pNewPrototype), удаляющую текущий прототип и задающую его для входного аргумента. Вместе с классами стратегий неполная конкретизация обеспечивает замечательную свободу при разработке библиотек. Можно реализовать тощие (lean) главные ютассы, способные использовать дополнительные свойства и состоящие из минимальных стратегий. 1.9. Ког/(бинирование классов стратегий Наибольшую пользу приносят комбинации стратегий. Обычно хорошо разработанный класс использует несколько стратегий для реализации разных аспектов своего функционирования. Затем пользователь библиотеки выбирает нужный режим работы, комбинируя несколько классов стратегий. Например, рассмотрим процесс разработки класса для обобщенного интеллектуального указателя. (Полная реализация этого класса осуществлена в главе 7.) Допустим, что нам нужно указать два варианта проектного решения, используя стратегии: потоковую модель и проверку перед разыменованием. Затем мы реализуем шаблонный класс SmartPtr, использующий две стратегии. tempi ate < class т, template <class> class checkingPolicy, template <class> class TheadingModel > class SmartPtr; Класс SmartPtr имеет три шаблонных параметра - тип указателя и две стратегии. Внутри этого класса эти две стратегии детально описаны, так что ютасс SmartPtr становится хорошо продуманной оболочкой, интегрирующей две разные стратегии, а не жесткой, законсервированной реализацией. Разрабатывая класс SmartPtr таким образом, можно предоставить пользователю возможность конфигурировать его с помощью оператора typedef. typedef SmartPtr<widget, NoChecking, SingleThreaded> WidgetPtr; В рамках некоторых приложений можно определять и применять несколько классов интеллектуальных указателей. typedef SmatPtr<widget, EnforceNotNull, SingleThreaded> SafewidgetPtr; Две стратегии можно сформулировать следующим образом. Стратегия Checking. Шаблонный класс CheckingPol iсу<Т> должен содержать функцию-член Check, которую можно вызывать с параметром типа Т-. Перед разыменованием указателя класс SmartPtr вызывает функцию Check, передавая ей объект, на который ссылается данный указатель. Стратегия ThreadingModel. Шаблонный класс ThreadingModel<т> должен содержать внутренний тип, называемый Lock, конструктор которого получает ссылку т&. На протяжении всего существования объекта Lock все операции над объектами типа Т регистрируются. 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 |