А. Александреску - Современное проектирование на C++ (1119444), страница 55
Текст из файла (страница 55)
Можно начать с пустого массива, а затем каждый объект класса, производного от класса 5баре, добавит в него новый элемент. Почему нельзя использовать вектор? Идентификаторы типов являются целыми числами, поэтому их можно считать индексами вектора.
Это проше и быстрее, но ассоциативный массив все же лучше. Индексы в таком массиве не обязаны быть смежными. Кроме того, в векторе индексы могут быть только целыми числами, в то время как индексом ассоциативного массива может быть любой тип, для которого установлено отношение порядка. При обобщении нашего примера этот факт приобретает особое значение. Начнем с разработки класса 5бареяассогу, предназначенного для создания всех объектов классов, производных от класса 5ьаре. В реализации класса 5пареяастогу мы будем использовать ассоциативный массив из стандартной библиотеки зтб::вар. с1ааа 5пареяассогу ( Глава 8. Фабрики объвктов рцЫ1с: туреде1 5ьаре* (*сгеатезьареса11ьаск) О; рг1чате: туредет зад::шар<бит, сгеатезьареса11Ьасм> са11ьасхмар; рцЫ1с: // возвращает значение тгце, если регистрация прошла успешно Ьоо) яецэ'зтег5Ьаре(э'пт 5Ьарехд, СгеатезпареСа11Ьаск Сгеатенй); // возвращает значение тгце, если фигура 5Ьарехд // уже зарегистрирована Ьоо1 цпгецэ'зтегед5Ьаре(дпт 5Ьарехд); 5Ьаре+ Сгеате5Ьаре(бпт 5Ьаретд); ргд часе: са11Ьасймар са11Ьаскз Зто общая схема масштабируемой фабрики.
Фабрика является масштабируемой, поскольку при добавлении нового класса, производного от класса 5Ьаре, в код нс нужно вносить никаких изменений. Класс 5Ьаренастогу разделяет ответственность: каждая новая фигура должна зарегистрироваться в фабрике, вызвав функцию яец1з- тег5Ьаре и передав сй целочисленный идентификатор и указатель на функцию, соз- дающую объект. Обычно эта функция состоит всего иэ одной строки. 5ьаре'е сгеатеьдпеО ( гетцгп пеш ьбпе; ) 1 Реализация класса ~Лпе также должна зарегистрировать эту функцию в объекте класса 5Ьарерастогу, используемом в приложении.
Обычно доступ к этому объекту является глобальным.' Регистрация выполняется вместе с инициализацией. Связь класса Ь1 пе с фабрикой 5Ьарерастогу устанавливается следующим образом. // Модуль реализации класса ьэ'пе // создаем безымянное пространство имен, // чтобы сделать функцию невидимой из других модулей пашезрасе ( 5Ьаре' сгеатеь1пеО ( гетцгп пеш ьэ'пе; // идентификатор класса ьдпе сопят дпт ьхйе Х; // дойустим, что фабрика тЬе5Ьарерастогу— // фабрика синглтоиов(си. главу 6) сонат Ьоо1 гец1зтегед = тьезьарерастогу::хпзтапсеО .йец1зтегбьаре( ьхне, сгеатеьэпе); Благодаря воэможностям, предоставленным стацдартным ассоциативным массивом зтд::юар, класс 5Ьарерастогу реализуется легко, По существу, функции-члены класса 5Ьарерастогу переадресуют аргументы функции-члену са11Ьасй .
э Это устанавливает связь между фабриками объектов и синглтонами. Действительно, а большинстве случаев фабрики лачлютса синглтонами. Ниже а этой главе мы обсудим, как используются фабрики с сииглтонами, описанными а главе б. 224 Чаоть П. Компоненты Ьоо1 ЗЬарегассогу::яео1зсегЗЬаре((пс зЬаресд, сгеасезЬареса11Ьаск сгеасегп) ( гесигп са11Ьас)с .1пзегс( са11Ьаскмар: гыа1це суре(збаресд, сгеасекп)).зесопд; ) Ьоо1 зЬаресассогу::цпгей(зсегзбаре(1пс зЬаресд) ( гесцгп са11Ьасйз .егазе(зЬаретд) == 1; Если вы не знакомы с шаблонным классом зсд::юар, предыдущий код нужаается в пояснениях.
° Класс зсд::юар содержит пары, состоящие из ключей и данных. В нашем случае ключами являются целочисленные идентификаторы фигур, а данные состоят из указателя на функцию. Такие пары имеют тип зсд::раз г<сопзс зпс, сгеасезбареса1!ЬасК>. При вызове функции зпзегс нужно передавать ей объект этого типа. Поскольку это выражение слишком длинное, в классе зсд::вар используется его синоним ча1ое суре. В качестве альтернативы можно использовать также тип зсд::юане ра1г.
° Функция-член )пзегс возвращает другую пару, на этот раз состоящую из итератора (ссгклаюшегося на только что вставленный элемент) и булевской переменной, принимающей значение сгце, если значения в ассоциативном массиве до этого момента не было, и та1зе — в противном случае. ° Функция-член егазе возвращает количество удаленных элементов.
Функция-член сгеасезбаре просто извлекает указатель на функцию, соответствующий полученному идентификатору типа, и вызывает ее. Если возникает ошибка, генерируется исключительная ситуация. ЗЬаре~ зЬарекассогу::Сгеатевбаре(зпс збаретд) ( са11Ьаскмар::сопзс 1сегасог 1 = са11Ьаскз .Лпд(зЬаретд); 11 (з == са11Ьаскз .епдО) ( // не найден сбгоы зсд::гцпсзюе еггог("неизвестный идентификатор"); ) // вызываем функцию для создания объекта гесцгп ((->зесопд) и; ) Посмотрим, что дает нам этот простой класс.
Вместо громоздкого оператора зкп ссЬ мы получили динамическую схему, требующую, чтобы каждый тип регистрировался в фабрике. Это распределяет ответственность между классами. Теперь, определяя новый класс, производный от класса збаре, можно просто добавлягаь файлы, а не модифицировать их. 8.4. Идентификаторы типов Осталась одна проблема — управление идентификаторами типов, По-прежнему добавление новых идентификаторов типов требует дисциплинированности и централизованного контроля. Добавляя новый класс, программист обязан проверять все существующие идентификаторы типов, чтобы новый идентификатор не совпал со старыми. Если все же совпадение произошло, второй вызов функции-члена яео- 225 Глава 8. Фабрики объектов т вгегбйаре с тем же самым идентификатором не выполняется, и объект этого типа не созлается.
Эту проблему можно решить, выбрав для идентификаторов более мощный тип, чем тпт. В нашем проекте тип зпт вовсе не требуется, Нужны лишь типы, которые могут быть ключами ассоциативного массива, т.е. типы, полдерживаюгцие операторы == и <. (Вот почему следует применять ассоциативные массивы, а не векторы,) Например, идентификаторы типов можно хранить в виде строк, считая, что каждый класс представлен своим именем: идентификатором типа ь(пе является строка "ь(пе", типа ро) уооп -- строка "ро) удоп" и тд, Это минимизирует вероятность совпадения идентификаторов, поскольку имена классов уникальны.
Если вы проводите каникулы, изучая язык С++, предыдущий параграф будет для вас предупредительным сигналом. Давайте попробуем применить класс туре того! Класс зтд::туре (пто является частью информации о типах времени выполнения программы (Кцпйгпе Туре 1п(оппайоп — КТТ1)„предусмотренных языком С+и.. Ссылку на класс втд: г буре з'пбо можно получить, применив оператор туре(д к типу или выражению. Класс зтд::туре (пго содержит функциюошен ламе, возвращающую указатель типа сопзт сваг*, ссылающийся на имя типа. Указатель, возвращаемый оператором сурет депе) . ламе О, ссылается на строку "с) аз в ь(пе", что и требовалось.
Проблема состоит в том, что не все компиляторы поддерживают этот оператор. Способ, которым реализована функция гуре (пго::паже, позволяет применять ее лишь для отладки. Нет никакой гарантии, что данная строка действительно представляет собой имя класса, и, что еше хуже, нет никакой гарантии, по эта строка является слинственной в рамках приложения. (Да, пользуясь функцией всд:: туре зобо:: ламе, вы можете получить два класса с одним и тем же именем.) И совсем убийственный аргумент; нет гарантии, что имя типа постоянно. Никто не может обещать, что оператор гуре(д(ьзпе).ламе() вернет то же самое имя при повторном выполнении программы.
Живучесть реализации — важное качество фабрик, а функция втд::туре (пго::ламе является неусгиойчивой. Итак, хотя класс в гд:: суре з пЕо на первый взгляд подходит для создания фабрик, на самом деле он совершенно неприемлем. Вернемся к вопросу об управлении идентификаторами типов. Генерировать идентификаторы можно с помощью датчика случайных чисел. Этот датчик нужно вызывать каждый раз, когда создается новый объект. При этом новое значение следует запомнить и больше никогда не изменять.' Это решение кажется довольно примитивным, однако вероятность того, что за тысячу лет работы датчик выдаст повторяющееся значение, равно 10 ". Итак, можно сделать единственный вывод; управление идентификаторами типов не входит в когипетенцию фабрики объектов.
Поскольку язык С- -ь не может гарантировать уникальность и устойчивосп, илентификатора типов, управление ими выходит за рамки языка, и ответственность за его реализацию следует возложить на программиста. з Фабрика СОМ-объектов, разработанная компанией М!сгоюй, использует именно такой подход. Оиа содержит алгоритм, генерирующий уникальный !28-битовый идентификатор, называемый глобатьиым уникальным идентификатором (О!оьа! Опгяие 1депйбег — ОИО) СОМ- объектов.
Этот алгоритм основан иа уникальности серийного номера сетевой карты или при отсутствии карты латы, времени и лругих переменных параметров, характеризующих состояние компьютера. Часть Еи Компоненты Мы описали все компоненты типичной фабрики объектов и привели прототип реализации. Перейдем теперь к следуюшему этапу — от частного к обшему.
Затем, обогатившись знаниями, вернемся к частному. 8.5. Обобщение Перечислим элементы, которые мы упомянули, обсуждая фабрики объектов. Это даст нам пишу лля размышлений при создании обобшенной фабрики объектов. ° Конкретное изделие (сопсгеге ргодцс1). Фабрика производит изделие в виде объекта. ° Абсглрактное изделие (аЬзсгасс ргос)цсс). Изделие создается на основе наследования базового типа (в нашем примере — класса зЬаре). Изделие — это обьект, тип которого принадлежит определенной иерархии.
Базовый тип этой иерархии представляет собой абстрактное изделие. Фабрика обладает полиморфным поведением, т.е. она возвращает указатель на абстрактное изделие, не передавая знания о типе конкретного изделия. ° Иденяификан~ар лшяа изделия (ргодцсг гуре )депбйег).
Это объект, идентифицируюший тип конкретного изделия. Как уже указывалось, идентификатор типа необходим для создания изделия, поскольку система типов языка С++ является статической. ° Празвадиеель изделия (ргодцсг сгеагог). Функция или функтор специализируются на создании одного, точно заданного типа объектов. Производитель изделия моделируется с помощью указателя на функцию.
Обобшенная фабрика объединяет в себе все эти элементы для создания точно опреде- ленного интерфейса, а также задает по умолчанию параметры, характерные для наи- более широко распространенных ситуаций. На первый взгляд все перечисленные выше элементы можно преобразовать в шаб- лонные параметры класса рассогу. Однако есть одно исключение: конкретное изде- лие не обязано быль известным фабрике. Если бы мы должны были точно указывать тип конкретного изделия, то получили бы классы гассогу для каждого конкретного изделия отдельно. Это противоречит нашей цели — изолировать класс яассогу от конкретных типов.