А. Александреску - Современное проектирование на C++ (1119444), страница 7
Текст из файла (страница 7)
Глава 1. Разработка классов на основе стратегий функцию с именем Сгеате, возврагцаюгцую значение требуемого типа, что соответствует требованиям стратегии сгеатог. Посмотрим, как можно создать класс, применяющий стратегию сгеатог. Такой кчасс должен либо содержать три ранее определенных класса, либо наследовать их свойства. // код библиотеки тевр)ате <с1аьв сгеат(аппо)(су> с)аьа м(6детмападег : роЫ(с сгеат!оппо1! су ( ); Классы, использующие одну или несколько стратегий, называются главными зозассамиз ()зоз! с!аззез). В приведенном выше примере класс и!6детмападег является главным классом, обладающим одной стратегией.
Главные классы объединяют структуры и функциональные возможности своих стратегий в один составной модуль. При конкретизации (!пз!алба!!оп) шаблона и(6детмападег клиент передает ему искомую стратегию. // код приложения туребе1 ы(6детмападег < орнеысгеатог и(6дет» мум(6детмдг; Проанализируем возникший контекст, Если объекту класса мумзбдетмдг нужно создать объект класса избдет, он вызынает функцию сгеатеО из подобьекта стратегии орнепсгеатог<и!6дет>. Однако выбор стратегии создания объектов находится в компетенции пользонателя класса !я!6детмападег.
Эгот класс по определению позволяет сноим пользователям уточнять специфические аспекты своих функциональных возможностей. Вот в чем заключается суть проектирования классов на основе стратегий. 1.5. т. Рввливвция классов стратвгиет с помощью шаблонных пврвмвтров Как и в предыдущем примере, шаблонный аргумент стратегии зачастую излишен. Необходимость явно задавать шаблонный аргумент стратегии орнепСГеатог создает неудобство.
Обычно главный класс знает заранее или легко может установить шаблонный аргумент класса стратегии. В рассмотренном выше примере класс мз'6детмападег всегда управляет объектами, имеющими тип м)6дет, поэтому совершенно излишне и потенциально. небезопасно требовать, чтобы пользователь снова указывал его при конкретизации стратегии орнепсгеатог. В этом случае код библиотеки может использовать шабла«ные шаблонные параметры (тепзр!аге !егпр1аге рагагпе!егз) следующим образом. // код библиотеки тенр)ате <теер)ате <с1аез сгеате6> с)азз сгеатзопео1!су> с)азз и!6детмападег : роЫ з с сгеагзоппо)(су<м(6дет> Несмотря на внешний вид символ сгеате6 не относится к определению класса и!6детмападег.
Его нельзя использовать внутри класса ы(6детмападег — он прел- з Несмотря на то что с технической точки зрения главные классы являются шибле«ными, мы будем придерживаться денного выше определения, поскольку и главные классы, и шаблонные главные кчассы представляют собой одно и то же понятие.
Часть!. Методы 32 ставляет собой формальный аргумент стратегии сгеас1опро1з су (а не класса и1ддесмападег), поэтому его можно просто проигнорировать. В коле приложения достаточно задать имя шаблона в конкретизации класса изддесмападег. // код приложения суреде1 и(ддесмападег<орнепсгеасог> муи1ддесмдг; Использование шаблонных шаблонных параметров вместе с классами стратегий— не просто вопрос удобства. Очень важно, что главный класс имев~ доступ к шаблону, имея возможность конкретизировать его другим типом. Допустим, что объекту класса и1ддесмападег также нужно создать объект типа Оагдес, используя ту же стратегию созлания объектов. Тогда соответствующий код может выглядеть следующим образом.
// код библиотеки семр1асе <севр1асе <с1ава> с1аьа сгеас(опио11су> с1азв ы1ддесмападег: рцб11с ~геас(опио11су<ы1ддес> уозд Оововесп1пд0 ( паддес* ри = сгеас1опво11су<паддес>0 .сгеасе0; Дает ли использование стратегий какие-либо преимушества? На первый взгляд немного. Все реализации стратегии сгеасог тривиальны. Автор класса и1ддесмападег мог просто написать код, предназначенный для создания объектов, в виде подставляемой функции, не прибегая к шаблону.
Однако использование стратегий придает классу и1ддесмападег необычайную гибкость. Во-первых, при конкретизации класса и1ддесмападег стратегии можно изменять извне так же легко, как и шаблонные аргументы. Во-вторых, программист может задавать свои собственные стратегии, характерные для конкретного приложения.
Можно использовать оператор пеп, функцию па11ос, прототипы или особые библиотеки управления памятью, присущие только данной операционной системе. Ситуация выглядит л~ак, б)дсло класс и1ддесмападег представляет собой неболыиое устройство для автоиатичегкой генераиии кода, а нользавитель задает способ, конюрым он генерируел~ код. Для того чтобы облегчить жизнь разработчикам прикладных программ, автор класса и1 ддесмападег может определить набор часто применяемых стратегий и указать наиболее популярную стратегию в виде значения шаблонного аргумента, заданного по умолчанию. севр1асе <сепр1асе <с1азз> с1ава сгеас1опио11су = ОриепСгеасог> с1аьа 'н1ддесмападег ... Отметим, что стратегии значительно отличаются от виртуальных функций, которые обещают тот же эффект.
Обычно программист, реализующий класс, определяет функции высокого уровня через элементарные виртуальные функции (рпгппЬе Ипиа! Гцпспоп) и дает пользователю возможность замещать их поведение. Однако, как показано выше, стратегии основаны на более подробной информации о типах и статическом связывании. Эти два фактора представляют собой важные элементы проектирования, которое насыщено правилами, определяющими способ взаимодействия типов до залуска лрограииы на выполнение и диктующими, что можно делать, а по — нет.
Стратегии позволяю~ генерировать проекты, комбинируя варианты, не вникая в подробности их внутреннего устройства (т а гуреаз(е щаппег). Кроме того, поскольку связывание главного класса с его стратегиями Глава 1. Разработка классов на основе стратегий осуществляется во время компиляции, компактность и эффективносп полученного кода сравнима с его эквивалентами, разработанными вручную. Разумеется, свойства стратегий делают их неудобными для динамического связывания и бинарных интерфейсов, поэтому, по существу, интерфейсы и стратегии дополняют друг друга, а не конкурируют. 1.б.2.
)эеализация клаоооа стратегий с помощью шаблонных функций-членов Вместо использования шаблонных шаблонных параметров можно применять шаблонные функции-члены и обычные классы. Это значит, что реализация стратегии представляет собой простой класс (в противоположность шаблонному классу), содержащий один или несколько шаблонных членов. Например, можно переопределить стратегию сгеатог и задать обычный (нешаблонный) класс, солержащий шаблонную функцию сгеасе«т>.
Этот класс будет выглядеть следующим образом. зтгист орневсгеасог ( севр1ате «с1азз т> зсасзс т" сгеатеО гесигп пев т; Преимушество такого способа определения и реализации стратегии заключается в том, что он хорошо поддерживается старыми компиляторами, С другой стороны, стратегии, определенные таким образом, часто труднее объяснять, определять, реализовывать и применять. 1.6. Расширенные стратегии Стратегия СгеаСог описывает только одну функцию-член — СгеаСе. Однако в классе РСототуреСгеатог содержится на две функции больше: песргототуре и 5етРготосуре.
Проанализируем возникающий при этом контекст. Поскольку класс Ф ддетмападег наследует свой класс стратегии, а функции Сетягоеосуре и детягососуре являются открытыми членами класса Ргосогуресгеасог, эти две функции передаются классу Ф ддетмападег и непосрелственно доступны клиентам. Однако класс в)0детмападег обращается только к функции-члену сгеате. Это все, что ему нужно для обеспечения своих функциональных возможностей. В то же время пользователям доступен более богатый интерфейс. Пользователь, применяющий стратегию СгеаСог, основанную на прототипах, может написать следующий код. туре0еУ в1одетмападег«ягососуресгеасог> мувЫдесмападег; Фидес* рягосотуре = ...; мувИдесмападег вдг; вдг.весягососуре(рягососуре); используем объект вдг Если впоследствии пользователь решит применить другую стратегию создания объектов, компилятор точно определит точки, в которых используется интерфейс, Часть Е Методы 34 ориентированный на применение прототипов.
Именно это и требуется от хорошо продуманного проекта. Возникаюший контекст очень привлекателен. Клиенты, которым нужны расширенные стратегии, могут извлекать выгоды из более широких функциональных возможностей, не изменяя базовых функциональных свойств класса-владельца. Не забывайте, что пользователи (но не библиотека) определяют, какую стратегию применяет класс.
В отличие от обычных множественных интерфейсов, стратегии дают пользователю возможность добавлять новые функциональные возможности главного класса, не вдаваясь в подробности его устройства. 1.7. Деструкторы классов стратегий Разрабатывая классы стратегий, следует учитывать одну важную деталь.
Чаше всего при создании классов, производных от своих стратегий, главный класс использует открытый интерфейс. По этой причине пользователь может автоматически конвертировать главный класс в стратегию, а затем удалить этот указатель с помошью оператора де1ете. Если в классе стратегии не определен виртуальный деструктор, применение оператора де1ете к указателю на объект этого класса приводит к непредсказуемым последствиялса туреде1 мт'ддетмвпадег<ргототуресгеатог> мумзбдетмападег; мумтбдетмападег вм; Ргототуресгеатог<мт'ддет>' рсгеатог = бмм; // сомнительно, но можно.
де1ете рСгеатог; // прекрасно компилируется, // но приводит к непредсказуемым последствиям. Однако определение виртуального деструктора для стратегии противоречит ее ста- тической природе и снижает производительность. Многие стратегии вообше не со- держат данных-членов, определяя, по сути, лишь функциональные возможности. Лю- бая виртуальная функция приводит к избыточному размеру объектов данного класса, поэтому применения виртуального конструктора следует избегать.