Анимация
JavaScript
|
Главная Библионтека функцию с именем create, возвращающую значение требуемого типа, что соответствует требованиям стратегии Creator. Посмотрим, как можно создать класс, применяющий стратегию Creator. Такой класс должен либо содержать три ранее определенных класса, либо наследовать их свойства. Код библиотеки template <class creationPolicy> class widgetManager : public CreationPolicy Классы, использующие одну или несколько стратегий, называются главными классами (host classes). В приведенном выще примере класс widgetManager является главным классом, обладающим одной стратегией. Главные классы объединяют структуры и функциональные возможности своих стратегий в один составной модуль. При конкретизации (instantiation) шаблона widgetManager клиент передает ему искомую стратегию. код приложения typedef WidgetManager < 0pNewCreator<widget> > MywidgetMgr; Проанализируем возникший контекст. Если объекту класса MywidgetMgr нужно создать объекг класса widget, он вызывает функцию CreateC) из подобъекта стратегии OpNewCreator<widget>. Однако выбор стратегии создания объектов находится в компетенции пользователя класса widgetManager. Этот класс по определению позволяет своим пользователям уточнять специфические аспекты своих функциональньге возможностей. Вот в чем заключается суть проектирования классов на основе стратегий. 1.5.1. Реализация классов стратегий с помощью шаблонных параметров Как и в предыдущем примере, шаблонный аргумент стратегии зачастую излишен. Необходимость явно задавать шаблонный аргумент стратегии OpNewCreator создает неудобство. Обычно главный класс знает заранее или легко может установить шаблонный аргумент класса стратегии. В рассмотренном выше примере класс widgetManager всегда управляет объектами, имеющими тип widget, поэтому совершенно излишне и потенциально, небезопасно требовать, чтобы пользователь снова указывал его при конкретизации стратегии OpNewCreator. В этом случае код библиотеки может использовать шаблонные шаблонные параметры (template template parameters) следующим образом. код библиотеки template <template <class Created> class creationPolicy> class WidgetManager : public CreationPolicy<widget> { Несмотря на внешний вид символ Created не относится к определению класса WidgetManager. Его нельзя использовать внутри класса WidgetManager- он пред- Несмотря на то что с технической точки зрения главные классы являются шаблонными, мы будем придерживаться данного выше определения, поскольку и главные классы, и шаблонные главные классы представляют собой одно и то же понятие. ставляет собой формальный аргумент стратегии CreationPolicy (а не класса widget-Manager), поэтому его можно просто проигнорировать. В коде приложения достаточно задать имя шаблона в конкретизации класса widgetManager. код приложения typedef widgetManager<OpNewCreator> MywidgetMgr; Использование шаблонных шаблонных параметров вместе с классами стратегий - не просто вопрос удобства. Очень важно, что главный класс имеет доступ к шаблону, имея возможность конкретизировать его другим типом. Допустим, что объекту класса WidgetManager также нужно создать объект типа Garget, используя ту же стратегию создания объектов. Тогда соответствующий код может выглядеть следующим образом. код библиотеки template <template <class> class CreationPolicy> class WidgetManager : public CreationPolicy<widget> { void DoSomethingO { Gadget* pw = CreationPolicy<Gadget>().CreateC); Дает ли использование стратегий какие-либо преимущества? На первый взгляд немного. Все реализации стратегии Creator тривиальны. Автор класса WidgetManager мог просто написать код, предназначенный для создания объектов, в виде подставляемой функции, не прибегая к шаблону. Однако использование стратегий придает классу WidgetManager необычайную гибкость. Во-первых, при конкретизации класса widgetManager стратегии можно изменять извне так же легко, как и шаблонные аргументы. Во-вторых, профаммист может задавать свои собственные стратегии, характерные для конкретного приложения. Можно использовать оператор new, функцию mal 1 ос, прототипы или особые библиотеки управления памятью, присущие только данной операционной системе. Ситуация выглядит так, будто класс WidgetManager представляет собой небольшое устройство для автоматической генерации кода, а пользователь задает способ, которым он генерирует код. Для того чтобы облегчить жизнь разработчикам прикладных профамм, автор класса WidgetManager может определить набор часто применяемых стратегий и указать наиболее популярную стратегию в виде значения шаблонного аргумента, заданного по умолчанию. template <template <class> class CreationPolicy = OpNewCreator> class WidgetManager ... Отметим, что стратегии значительно отличаются от виртуальных функций, которые обещают тот же эффект. Обычно профаммист, реализующий класс, определяет функции высокого уровня через элементарные виртуальные функции (primitive virtual function) и дает пользователю возможность замещать их поведение. Однако, как показано выше, стратегии основаны на более подробной информации о тиПах и статическом связывании. Эти два фактора представляют собой важные элементы проектирования, которое насыщено правилами, определяющими способ взаимодействия типов до запуска програ\шы на выполнение и диктующими, что можно делать, а irro - нет. Стратегии позволяют генерировать проекты, комбинируя варианты, не вникая в подробности их внутреннего устройства (in а typesafe manner). Кроме того, поскольку связывание главного класса с его стратегиями осуществляется во время компиляции, компактность и эффективность полученного кода сравнима с его эквивалентами, разработанными вручную. Разумеется, свойства стратегий делают их неудобными для динамического связывания и бинарных интерфейсов, поэтому, по существу, интерфейсы и стратегии дополняют друг друга, а не конкурируют. 1.5.2. Реализация классов стратегий с помощью шаблонных функций-членов Вместо использования шаблонных шаблонных параметров можно применять шаблонные функции-члены и обычные классы. Это значит, что реализация стратегии представляет собой простой класс (в противоположность шаблонному классу), содержащий один или несколько шаблонных членов. Например, можно переопределить стратегию Creator и задать обычный (нешаблонный) класс, содержащий шаблонную функцию Create<T>. Этот класс будет выглядеть следующим образом. struct OpNewCreator template <c1ass т> static т* CreateC) { return new T; Преимущество такого способа определения и реализации стратегии заключается в том, что он хорошо поддерживается старыми компиляторами. С другой стороны, стратегии, определенные таким образом, часто труднее объяснять, определять, реализовы-вать и применять. 1.6. Расширенные стратегии Стратегия Creator описывает только одну функцию-член - Create. Однако в классе PtototypeCreator содержится на две функции больше: GetPrototype и Set-Prototype. Проанализируем возникающий при этом контекст. Поскольку класс WidgetManager наследует свой класс стратегии, а функции GetPrototype и SetPrototype являются открытыми членами класса Prototypecreator, эти две функции передаются классу WidgetManager и непосредственно доступны клиентам. Однако класс WidgetManager обращается только к функции-члену Create. Это все, что ему нужно для обеспечения своих функциональных возможностей. В то же время пользователям доступен более богатый интерфейс. Пользователь, применяющий стратегию Creator, основанную на прототипах, может написать следующий код. typedef widgetManager<PrototypeCreator> MyWidgetManager; widget* pprototype = ... ; MyWidgetManager mgr; mgr.SetPrototypeCpPrototype); ... используем объект mgr ... Если впоследствии пользователь решит применить другую стратегию создания объектов, компилятор точно определит точки, в которых используется интерфейс, 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 |