Анимация
JavaScript
|
Главная Библионтека public: typedef Shape* C*CreateShapeCanback) () ; private: typedef std::map<int, CreateShapeCanback> CallbackMap; public: возвращает значение true, если регистрация прошла успешно bool RegisterShapeCint Shapeld, CreateShapeCallback CreateFn); возвращает значение true, если фигура Shapeld уже зарегистрирована bool unregisteredShapeCint Shapeld); Shape* CreateShapeCint Shapeld); private: CallbackMap callbacks.; Это общая схема масштабируемой фабрики. Фабрика является масштабируемой, поскольку при добавлении нового класса, производного от класса Shape, в код не нужно вносить никаких изменений. Класс shapeFactory разделяет ответственность: каждая новая фигура должна зарегистрироваться в фабрике, вызвав функцию Regis-terShape и передав ей целочисленный идентификатор и указатель на функцию, создающую объект. Обычно эта функция состоит всего из одной строки. Shape*& CreateLineC) return new Line; Реализация класса Line также должна зарегистрировать эту функцию в объекте класса ShapeFactory, используемом в приложении. Обычно доступ к этому объекту является глобальным. Регистрация выполняется вместе с инициализацией. Связь класса Line с фабрикой ShapeFactory устанавливается следующим образом. Модуль реализации класса Line Создаем безымянное пространство имен, чтобы сделать функцию невидимой из других модулей namespace Shape* CreateLineC) return new Line; идентификатор класса Line const int LINE =1; допустим, что фабрика TheShapeFactory - фабрика синглтоновСсм. главу 6) const bool registered = TheShapeFactory::instanceC).RegisterShapeC line, CreateLine); Благодаря возможностям, предоставленным стандартным ассоциативным массивом std: :map, класс ShapeFactory реализуется легко. По существу, функции-члены класса ShapeFactory переадресуют аргументы функции-члену callback . Это устанавливает связь между фабриками объектов и синглтонами. Действительно, в большинстве случаев фабрики являются синглтонами. Ниже в этой главе мы обсудим, как используются фабрики с синглтонами, описанными в главе 6. bool ShapeFactory:-.RegisterShapeCint shapeld, CreateShapeCallback createFn) return callback .insert( CallbackMap::value type(shapeld, createFn)).second; bool ShapeFactory::unregisterShape(int shapeld) return callbacks .erase(shapeid) == 1; Если вы не знакомы с шаблонным классом std: :map, предыдуший код нуждается в пояснениях. • Класс std::тар содержит пары, состояшие из ключей и данных. В нашем случае ключами являются целочисленные идентификаторы фигур, а данные состоят из указателя на функцию. Такие пары имеют тип std::pai r<const int, CreateShapeCal 1 back>. При вызове функции insert нужно передавать ей объект этого типа. Поскольку это выражение слишком длинное, в классе std: :map используется его синоним value type. В качестве альтернативы можно использовать также тип std: :make pai г. • Функция-член insert возвращает другую пару, на этот раз состоящую из итератора (ссылающегося на только что вставленный элемент) и булевской переменной, принимающей значение true, если значения в ассоциативном массиве до этого момента не было, и false - в противном случае. • Функция-член erase возвращает количество удаленных элементов. Функция-член CreateShape просто извлекает указатель на функцию, соответствующий полученному идентификатору типа, и вызывает ее. Если возникает ошибка, генерируется исключительная ситуация. Shape* ShapeFactory::Createshape(int shapeld) CallbackMap::const iterator i = callbacks .find(shapeld); if (i == callbacks .end()) не найден throw std::runtime error("нeизвecтный идентификатор"); вызываем функцию для создания объекта return (i->second)О; Посмотрим, что дает нам этот простой класс. Вместо громоздкого оператора swi tch мы получили динамическую схему, требующую, чтобы каждый тип регистрировался в фабрике. Это распределяет ответственность между классами. Теперь, определяя новый класс, производный от класса shape, можно просто добавлять файлы, а не модифицировать их. 8.4. Идентификаторы типов Осталась одна проблема - управление идентификаторами типов. По-прежнему добавление новых идентификаторов типов требует дисциплинированности и централизованного контроля. Добавляя новый класс, программист обязан проверять все существующие идентификаторы типов, чтобы новый идентификатор не совпал со старыми. Если все же совпадение произошло, второй вызов функции-члена Reg-Глава 8. Фабрики объектов 225 isterShape с тем же самым идентификатором не выполняется, и объект этого типа не создается. Эту проблему можно решить, выбрав для идентификаторов более мошный тип, чем int. В нашем проекте тип int вовсе не требуется. Нужны лишь типы, которые могуг быть ключами ассоциативного массива, т.е. типы, поддерживающие операторы == и <. (Вот почему следует применять ассоциативные массивы, а не векторы.) Например, идентификаторы типов можно хранить в виде строк, считая, что каждый класс представлен своим именем: идентификатором типа Line является строка "Line", типа Polygon - строка "Polygon" и т.д. Это минимизирует вероятность совпадения идентификаторов, поскольку имена классов уникальны. Если вы проводите каникулы, изучая язык С++, предыдущий парафаф будет для вас предупредительным сигналом. Давайте попробуем применить класс type info! Класс std: :type info является частью информации о типах времени выполнения программы (Runtime Туре Infomiation - RTT1), предусмотренных языком С++. Ссьшку на класс std: :type info можно получить, применив оператор typeid к типу или выражению. Класс std: :type info содержит функцию-член name, возвращающую указатель типа const char*, ссьыающийся на имя типа. Указатель, возвращаемый оператором typeidCLine) . nameC), ссьыается на строку "class Line", что и требовалось. Проблема состоит в том, что не все компиляторы поддерживают этот оператор. Способ, которым реализована функция type info: iname, позволяет применять ее лгапь для отладки. Нет никакой гарантии, что данная строка действительно представляет собой имя класса, и, что еще хуже, нет никакой гарантии, что эта строка является единственной в рамках приложения. (Да, пользуясь функцией std:: type info::name, вы можете получить два класса с одним и тем же именем.) И совсем убийственный аргумент: нет гарантии, что имя типа постоянно. Никто не может обещать, что оператор typeidCLine).nameC) вернет то же самое имя при повторном выполнении программы. Живучесть реализации - важное качество фабрик, а функция std: :type info: :name является неустойчивой. Итак, хотя класс std: :type info на первый взгляд подходит для создания фабрик, на самом деле он совершенно неприемлем. Вернемся к вопросу об управлении идентификаторами типов. Генерировать идентификаторы можно с помошью датчика случайных чисел. Этот датчик нужно вызывать каждый раз, когда создается новый объект. При этом новое значение следует запомнить и больше никогда не изменять. Это решение кажется довольно примитивным, однако вероятность того, что за тысячу лет работы датчик выдаст повторяющееся значение, равно 10~°. Итак, можно сделать единственный вывод: управление идентификаторами типов не входит в компетенцию фабрики объектов. Поскольку язык С++ не может гарантировать уникальность и устойчивость идентификатора типов, управление ими выходит за рамки языка, и ответственность за его реализацию следует возложить на программиста. Фабрика СОМ-объектов, разработанная компанией Microsoft, использует именно такой подход. Она содержит алгоритм, генерирующий уникальный 128-битовый идентификатор, называемый глобальным уникальным идентификатором (Global Unique Identifier - GUID) СОМ-объектов. Этот алгоритм основан на уникальности серийного номера сетевой карты или при отсутствии карты даты, времени и других переменных параметров, характеризующих состояние компьютера. 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 |