Анимация
JavaScript
|
Главная Библионтека ются динамическими - их поведение можно изменять, не модифицируя вызывающий код. В противоположность этому, каждое создание объекта - это камень преткновения для статического, жестко определенного кода. В результате вызовы виртуальных функций связывают вызывающий код только с интерфейсом (базовым классом). Объектно-ориентированное профаммирование стремится снять офаничения, накладываемые необходимостью указывать фактический тип создаваемого объекта. Однако, по крайней мере в языке С++, создание объектов по-прежнему связывает вызывающий код с конкретным классом, находящимся на самом дне иерархиии. На самом деле эта проблема намного глубже: даже в повседневной жизни создать нечто и пользоваться им - совершенно разные вещи. Обычно предполагается, что, создавая определенный объект, вы точно знаете, что будете с ним делать. И все же иногда в этом правиле возникают исключения. Это происходит в следующих ситуациях. • Если точное знание о каком-то объекте нужно передать другой сущности. Например, вместо непосредственного вызова оператора new можно вызвать виртуальную функцию create, принадлежащую объекту более высокого иерархического уровня, позволяя пользователям изменять его поведение с помощью полиморфизма. • Если знания об объекте не могут быть выражены с помощью средств языка С++. Например, пусть задана строка "Derived". В этом случае профаммист точно знает, что нужно создать объект типа Derived, однако эту строку невозможно передать оператору new в качестве имени типа. Для рещения этих принципиально важных задач предназначены фабрики объектов. В этой главе обсуждаются следующие темы. • Ситуации, в которых необходимы фабрики об;ьектов. • Почему виртуальные конструкторы трудно реализовать в языке С++. • Как создавать объекты, задавая их типы в виде значений. • Реализация обобщенной фабрики объектов. В конце главы мы построим обобщенную фабрику объектов. Ее можно настраивать по типу, способу создания и методу идентификации объектов. Фабрику объектов можно использовать в сочетании с другими компонентами, описанными в этой книге, например, синглтонами (глава 6) - для создания фабрики объектов внутри приложения и функторами (глава 5) - для настройки работы фабрики. Мы рассмотрим фабрику клонов, которая может создавать дубликаты объектов любого типа. 8.1. Для чего нужны фабрики объектов Фабрики объектов нужны в двух случаях. Во-первых, когда библиотека должна не только манипулировать готовыми объектами, но и создавать их. Например, представьте себе, что мы разрабатываем интегрированную среду для многооконного текстового редактора. Поскольку эта среда должна быть легко расширяемой, мы предусматриваем абстрактный класс Document, на основе которого пользователи могут создавать производные классы TextDocument и HTMLDocument. Другой компонент интефированной среды - класс DocumentManager, хранящий список всех открытых документов. Следует установить правило: каждый документ, существующий в приложении, должен быть известен классу DocumentManager. Следовательно, создание нового документа тесно связано с его вставкой в список документов, хранящийся в классе DocumentManager. Когда две операции настолько тесно связаны, лучше всего описать их с помошью одной и той же функции и никогда не выполнять по отдельности. class DocumentManager { public: Document* NewDocumentC); private: virtual Document* CreateDocumentC) = 0; std::list<Document*> listOfDocs ; Document* DocumentManager::NewDocumentC) { Document* pDoc = CreateDocumentC); listOfDocs .push backCpDoc); return pDoc; Функция-член createDocument заменяет вызов оператора new. Функция-член NewDocument не может использовать оператор new, поскольку при написании Ютасса DocumentManager еше неизвестно, какой конкретно документ будет создан. Для того чтобы использовать интегрированную среду, программист создает производный класс на основе класса DocumentManager и замешает виртуальную функцию-член CreateDocument (которая должна быть чисто виртуальной). В книге GoF (Gamma et al., 1995) метод CreateDocument называется фабрикой (factory method). Поскольку в производном классе точно известен тип создаваемого метода, оператор new можно вызывать непосредственно. Таким образом, в интегрированной среде не обязательно хранить информацию о типе создаваемого документа и можно оперировать только базовым классом Document. Замешение функций-членов осушествляет-ся очень просто и, по сути, сводится к вызову оператора new. Document* GraphicDocumentManager::CreateDocumentC) { return new GraphicDocument; В TO же время приложение, построенное на основе этой интегрированной среды, может поддерживать создание нескольких типов документов (например, растровую и векторную графику). В этом случае замешенная функция-член CreateDocument может выводить на экран диалоговое окно, предлагая пользователю ввести конкретный тип создаваемого документа. Открытие документа, раее сохраненного на диске, создает новую и более сложную проблему, которую можно решить с помошью фабрики объектов. Записывая объект в файл, его фактический тип следует сохранять в виде строки, целого числа или какого-нибудь идентификатора. Таким образом, хотя информация о типе сушествует, форма ее хранения не позволяет создавать объекты языка С-Ы-. Эта задача носит обший характер и связана с созданием объектов, тип которых определяется во время выполнения программы: вводится пользователем, считывается из файла, загружается из сети и т.п. В этом случае связь между типом и значениями еше глубже, чем в полиморфизме: используя полиморфизм, сушность, манипулируюшая объектом, ничего не знает о его типе; однако сам объект имеет точно определенный тип. При считывании объектов из места их постоянного хранения тип отрывается от объекта и должен сам преобразовываться в некий объект. Кроме того, считывать объект из места его хранения следует с помощью виртуальной функции. Создание объектов из "чистой" информации о типе и последующее преобразование динамической информации в статические типы языка С++ - важная проблема при разработке фабрик объектов. Она рассматривается в следующем разделе. 8.2. Фабрики объектов в языке С++: классы и объекты Чтобы найти рещение проблемы, нужно хорощо в ней разобраться. В этом разделе мы попытаемся найти ответы на следующие вопросы. Почему конструкторы в языке С++ имеют такие жесткие офаничения? Почему в самом языке нет гибких средств для создания объектов? Поиск ответа на эти вопросы приводит нас к основам системы типов языка С++. Рассмотрим следующий оператор. Base* рв = new Derived; Для того чтобы понять, почему он имеет настолько жесткие офаничения, нам нужно ответить на два вопроса. Что такое класс и что такое объект? Эти вопросы вызваны тем, что основной причиной неудобств в приведенном выше операторе является имя класса Derived, которое требуется явно задавать в операторе new, хотя мы бы предпочли представить это имя в виде значения, т.е. объекта. В языке С++ классы и объекты - соверщенно разные сущности. Классы создаются программистом, а объекты - программой. Новый класс невозможно создать во время выполнения профаммы, а объект невозможно создать во время компиляции. Классы не имеют статуса: их нельзя копировать, хранить в переменной или возвращать из функции. Существуют языки, в которых классы являются объектами. В этих языках некоторые объекты с определенными свойствами по умолчанию рассматриваются как классы. Следовательно, в этих языках во время выполнения профаммы можно создавать новые классы, копировать их, хранить в переменных и т.д. Если бы язык С++ был одним из таких языков, можно было бы написать примерно такой код. предупреждение - это не с++ Будем считать, что class - это класс и объект одновременно Class ReadCconst char* fileName); Document* DocumentManager:lOpenDocumentCconst char* fileName) { Class theClass = ReadCfileName); Document* pDoc = new theClass; Таким образом, переменную типа class можно было бы передавать оператору new. В рамках этой парадигмы передача известного имени класса оператору new ничем не отличалась бы от явного указания константы. В таких динамических языках за счет потери гибкости достигнут определенный компромисс между типовой безопасностью и эффективностью, поскольку статическая типизация - это важный компонент оптимизации кода. В языке С++ принят прямо противоположный подход - опора на статическую систему типов, позволяющая достигать максимальной гибкости. Итак, создание фабрик объектов в языке С++ представляет собой сложную задачу. В языке С++ между типами и значениями лежит пропасть: значение имеет тип, но 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 |