Анимация
JavaScript
|
Главная Библионтека Другие, более сложные реализации стратегии FactoryError могут искать идентификатор типа и возвращать указатель на подходящий объект, возвращать нулевой указатель (если генерировать исключительную ситуацию нежелательно), генерировать объект исключительной ситуации или прекращать выполнение программы. Уточнять поведение класса можно с помощью новых реализаций стратегии FactoryError, передавая их в качестве четвертого аргумента класса Factory. 8.6. Мелкие детали На самом деле реализация класса Factory в библиотеке Loki не использует класс std: :map. Вместо него она применяет класс AssocVector, оптимизированный на редкие вставки и часто повторяющиеся операции поиска элементов. Эта ситуация характерна для класса Factory. Класс AssocVector подробно описан в главе 11. В первоначальном варианте класса Factory ассоциативный массив считался настраиваемым, поскольку был шаблонным параметром. Однако часто класс AssocVector полностью удовлетворяет всем требованиям класса Factory. Кроме того, использование стандартных контейнеров в качестве шаблонных шаблонных параметров не соответствует стандарту языка. Вот почему программисты, занимающиеся реализацией стандартных контейнеров, могут добавлять новые шаблонные аргументы, задавая их значения по умолчанию. Сосредоточим теперь хвое внимание на шаблонном параметре Productcreator. Во-первых, он должен обладать функциональным поведением (допускать применение оператора О и не иметь аргументов) и возвращать указатель, который можно преобразовывать в тип AbstractProduct*. В конкретной реализации, показанной выше, объект класса productcreator был просто указателем на функцию. Этого достаточно, если нужно создавать объекты, вызывая оператор new, как это бывает в большинстве случаев. Следовательно, в качестве типа Productcreator, задаваемого по умолчанию, можно выбрать следующий: AbstractProduct* (*)() Этот тип выглядит весьма странно, так как не имеет имени. Если после звездочки указать имя, поместив его внутри скобок, тип станет указателем на функцию, не получающую никаких параметров и возвращающую указатель на класс AbstractProduct. Если все это вас по-прежнему удивляет, обратитесь к главе 5, в которой обсуждаются указатели на функцию. Кстати, в этой главе описан очень интересный шаблонный параметр, который можно передавать классу Factory в качестве параметра productcreator, а именно: Functor<AbstractProduct*>. Этот параметр обеспечивает отличную гибкость: можно создавать объекты, вызывая простую функцию, функцию-член или функтор, а также связывать с ними соответствующие параметры. Код, обеспечивающий связь между ними, содержится в классе Functor. Объявление шаблонного класса Factory выглядит следующим образом. template < class AbstractProduct, class IdentifierType, class Productcreator = AbstractProduct* (*)(), tempiate<typename, class> class FactoryErrorpolicy = DefaultFactoryError > class Factory; Теперь класс Factory полностью готов. 230 Часть II. Компоненты 8.7. Фабрика клонирования Несмотря на то что генетические фабрики, клонирующие универсальных солдат, - идея стращноватенькая, клонировать объекты языка С++ - занятие соверщенно безвредное и даже полезное. Цель клонирования объектов, которую мы рассмотрим, несколько отличается от того, что мы имели в виду раньще: мы больще не должны создавать объекты с самого начала. У нас есть указатель на полиморфный объект, и мы хотели бы создать точную копию этого объекта. Поскольку тип полиморфного объекта точно не известен, мы на самом деле не знаем, какой именно объект создать. В этом и заключается проблема. Поскольку оригинал у нас под рукой, можно применить классический полиморфизм. Следовательно, при клонировании объекта нужно объявить в базовом классе виртуальную функцию-член Clone и заместить ее в производном классе. Рассмотрим клонирование объектов на примере иерархии геометрических фигур. class Shape { public: virtual shape* CloneC) const = 0; class Line : public Shape public: virtual Line* CloneC) const { return new LineC*this); Функция-член Line::Clone не возвращает указатель на класс Shape, поскольку мы применили свойства языка С++, называемые ковариантными типами возвращаемых значений (covariant return types). Благодаря этому замещенная виртуальная функция может возвращать указатель на производный класс, а не на базовый. Теперь, следуя идиоме, мы должны реализовать аналогичную функцию Clone в каждом классе, который добавляется в иерархию. Эти функции имеют одно и то же содержание: создается объект класса Poligon, возвращается указатель new polygonC*this) и т.д. Эта идиома работает, но имеет ряд недостатков. • Если базовый класс изначально не предназначался для клонирования (в нем не объявлялась виртуальная функция, эквивалентная функции Clone) и модификации, то эту идиому применять нельзя. Такая ситуация возникает, например, когда вы нищете приложение, используя в качестве базовых классы из какой-нибудь библиотеки. • Даже если все классы можно модифицировать, эта идиома требует особой осторожности. Отсутствие реализации функции Clone в производных классах компилятор не замечает, что может привести в непредсказуемым последствиям при выполнении программы. Первый недостаток очевиден, поэтому перейдем к обсуждению второго. Представим себе, что, создавая класс DottedLine, производный от класса Line, мы забыли заместить функцию DottedLine::Clone. Кроме того, допустим, что у нас есть указа- тель на объект класса shape, который на самом деле ссылается на объект класса Dot-tedLine, и мы вызываем из этого объекта функцию Clone. Shape* pShape; Shape* pDuplicateShape = pShape->CloneC); Вызывается функция Line: :Clone, возвращающая объект класса Line. Это очень неприятно, поскольку мы предполагали, что указатель pDuplicateShape имеет тот же динамический тип, что и указатель pShape. Оказывается, это совсем не так, что может вызвать массу проблем - от вывода неожиданных типов до краха приложения. К сожалению, надежных средств, позволяющих компенсировать второй недостаток, не существует. На языке С++ нельзя сказать: "Я определяю эту функцию и хочу, чтобы она замешалась во всех производных классах". Если вы не хотите неприятностей, придется определить замещаемую функцию Clone в каждом классе. Если указанную идиому немного усложнить, можно организовать приемлемую проверку типа во время выполнения программы. Сделаем функцию Clone открытой и невиртуальной. Внутри класса она будет вызывать закрытую виртуальную функцию Do-Clone, гарантируя эквивалентность динамических типов. Соответствующий код проще, чем его объяснение. class Shape { publi с: Shape* CloneO const Невиртуальная функция { переадресовываем вызов функции DoClone Shape* pClone = DoCloneO; проверяем эквивалентность типов (этот тест может быть сложнее, чем макрос assert) assertCtypeidC*pClone) == typeidC*this)); return pclone; private: virtual shape* DoCloneO const = 0; Закрытая функция Единственным недостатком этого подхода является тот факт, что теперь нельзя использовать ковариантные типы возвращаемых значений. Все наследники класса Shape должны замещать функцию DoClone, оставляя ее закрытой, чтобы клиенты не могли вызвать ее. Функция Clone должна оставаться в стороне. Клиенты используют только функцию Clone, осуществляющую проверку типов во время выполнения программы. Очевидно, что и здесь мы не гарантированы от появления программистских ошибок, например, замещения функции Clone или объявления функции DoClone открытой. Не забывайте, что если все классы, входящие в иерархию, изменить невозможно (такая иерархия называется закрытой), и если они не предназначены для клонирования, эту идиому реализовать все равно не удастся. Такая ситуация встречается довольно часто, поэтому нам следует поискать альтернативу. На помощь может прийти специальная фабрика объектов. Она свободна от недостатков, указанных выше, но немного снижает производительность программы - вместо виртуального вызова выполняется поиск по карте и вызов через указатель на функцию. Поскольку количество классов приложения на самом деле никогда не бы- 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 |