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