Шаблон фабрики объектов
возможностью преобразования продуктов в последовательную форму. Хотелось бы доработать его, довести до максимально гибкого состояния, чтобы можно было легко использовать в других проектах. Поэтому и решил выложить его на обозрение общественности. Исходники находятся в репозитории Subversion по адресу:
http://subversion.assembla.com/svn/djilyqEmSr3iSfabIlDkbG
Клиент для windows можно скачать отсюда:
http://tortoisesvn.net/downloads
Проект под VS2005, unmanaged C++. Никаких дополнительных библиотек для тестовой программы не требуется.
Кратко опишу сущности, которые я ввел для шаблона:
1. Создатель - сущность, производящая и сериализирующая продукт.
2. Продукт - собственно то, что производится создателем.
3. Фабрика - реестр создателей. Каждому создателю при регистрации присваивается идентификатор, по которому и можно создавать продукты.
4. Упаковка - пара "продукт/идентификатор создателя". Это и есть результат создания продукта через фабрику.
Для сериализации, помимо самого продукта, требуется информация о его создателе, поэтому и введена данная
сущность.
Исходник был в проекте под Qt. В качестве хранилища использовался класс QSettings, в качестве идентификаторов создателей QUuid. Но отрабатывать шаблон лучше на STL, поэтому и набросал простенький
примерчик с уточняющей реализацией под потоки ввода/вывода.
В качестве продукта выбрана строка (std::string), идентификатора - int.
Синонимы типов идентификатора, создателя и фабрики:
typedef tlib::factory::Stl_store_creator<string, id_t> creator_t;
typedef tlib::factory::Stl_store_factory<creator_t> factory_t;
В примере регистрируется пара создателей, затем производятся упаковки, выводятся на экран и сохраняются в файле. Затем происходит загрузка упаковок из файла и вывод на экран.
В этом простейшем примере каждый создатель производит одну и ту же строку.
Также нужно отработать ситуацию при отсутствии требуемого создателя в реестре и многое другое.
Жду вашей критики и предложений...
1) Я бы всё-таки не стал смешивать сериализацию и фабрики объектов. Имхо, сериализовывать сложный объект, состоящий из кучи объектов, сконструированных таким образом, тяжело. Да и по смыслу как-то не очень. Если очень хочется такой сериализатор - надо создавать его как обычный объект, через фабрику.
2) Использовать везде передачу параметра по значению - это сурово. Особенно сурово так делать с std::map в Factory::get_creator_map(). Лучше максимально возможно использовать константные ссылки.
3) Не понял, зачем такие сложности с фабрикой - почему она шаблонная? Казалось бы, все CREATOR'ы наследуются от ICreator как раз для наличия "наибольшего общего делителя".
4) Использование CREATOR_PTR как задаваемого извне - это фатально. В данном примере Stl_store_factory определяет его как обычный сишный указатель. Если кто-то уничтожит объект Creator снаружи, то фабрика сможет сделать AV. Либо, что еще хуже, отработать с этим, уже удаленным объектом. Самое страшное, что удаление с AV могут произойти в сильно разнесенных по коду и времени местах. Отлаживать такое тяжело. Используй умные указатели, благо, у тебя есть доступ к базовому классу.
5) На мой взгляд, строк вполне хватает для идентификаторов, и делать их тип параметром шаблона излишне.
1) Я бы всё-таки не стал смешивать сериализацию и фабрики объектов. Имхо, сериализовывать сложный объект, состоящий из кучи объектов, сконструированных таким образом, тяжело. Да и по смыслу как-то не очень. Если очень хочется такой сериализатор - надо создавать его как обычный объект, через фабрику.
Т.е. вы предлагаете разделить одну фабрику на две: создающую объекты продуктов и объекты, сериализирующие продукты первой фабрики? Надо посмотреть, что получится, решение более гибкое.
2) Использовать везде передачу параметра по значению - это сурово. Особенно сурово так делать с std::map в Factory::get_creator_map(). Лучше максимально возможно использовать константные ссылки.
Здесь согласен. Factory::get_creator_map - первый кандидат на вылет, для запроса таблицы создателей можно будет применить более гибкий паттерн visitor. Но и пользоваться ссылками нужно тоже продумав все варианты. Например, в качестве продукта может быть ссылка на объект, а ссылка на ссылку в С++ не существует.
3) Не понял, зачем такие сложности с фабрикой - почему она шаблонная? Казалось бы, все CREATOR'ы наследуются от ICreator как раз для наличия "наибольшего общего делителя".
Шаблонная, потому что гибкая. Гибкая, потому что шаблонная. Не будь шаблона - приходилось бы использовать динамическое приведение типов вниз со всеми вытекающими последствиями.
4) Использование CREATOR_PTR как задаваемого извне - это фатально. В данном примере Stl_store_factory определяет его как обычный сишный указатель. Если кто-то уничтожит объект Creator снаружи, то фабрика сможет сделать AV. Либо, что еще хуже, отработать с этим, уже удаленным объектом. Самое страшное, что удаление с AV могут произойти в сильно разнесенных по коду и времени местах. Отлаживать такое тяжело. Используй умные указатели, благо, у тебя есть доступ к базовому классу.
В реализации для STL я намеренно сделал CREATOR_PTR как обычный указатель, т.к. в STL (в стандарте от 2003, в 2009м этот пробел заполнят ;-) ) нет умного указателя, подходящего для корректного хранения в контейнерах.
Это такое же фатальное как просто использовать глупые указатели в программе, от программиста в любом случае потребуется внимание при использовании таких указателей. Если вы не согласны с параметром по-умлочанию, вы всегда можете его изменить. Не внося изменений во внутренности фабрики я без всяких проблем могу перейти на умные указатели:
В я не стал использовать какую-то конкретную библиотеку внутри шаблона, т.к. это опять таки снижает гибкость шаблона. Может кто-то предпочетает, например, библиотеку loki.
Этот пример тоже идет в пользу аргумента: "Шаблонная, потому что гибкая".
5) На мой взгляд, строк вполне хватает для идентификаторов, и делать их тип параметром шаблона излишне.
И опять таки это сделано по причине того же аргумента.
У нас принято сериализацию реализовывать в самом объекте, передавая ему на вход указатель на интерфейс хранилища. Это не идеально гибкий способ, но, как показывает практика, в 99 процентах случаев хватает за глаза.
Кстати, я программист, ко мне можно на "ты" :)
Имеет ли смысл продукт в виде сишной ссылки?
Разве приведение потребуется? По-моему, только виртуальные вызовы.
Тут скорее моё субъективное предпочтение - если можно не использовать шаблоны-не используй.
Это такое же фатальное как просто использовать глупые указатели в программе, от программиста в любом случае потребуется внимание при использовании таких указателей. Если вы не согласны с параметром по-умлочанию, вы всегда можете его изменить. Не внося изменений во внутренности фабрики я без всяких проблем могу перейти на умные указатели:
В я не стал использовать какую-то конкретную библиотеку внутри шаблона, т.к. это опять таки снижает гибкость шаблона. Может кто-то предпочетает, например, библиотеку loki.
Этот пример тоже идет в пользу аргумента: "Шаблонная, потому что гибкая".
Этот тип не используется во внешнем интерфейсе (метод find не в счет, т.к. там можно вернуть и сишный указатель), а используется только во внутренних механизмах класса. На мой взгляд, здесь не тот случай, когда полезно отдавать наружу внутреннюю логику класса - никаких бонусов с этого нет, одни только грабли.
Кстати, я программист, ко мне можно на "ты"
На самом деле я не про сам процесс сериализации говорил, а об управляющей составляющей этого процесса.
Самым интересным было бы упростить процесс сериализации полиморфных объектов. Такая задача стоит, например, при использовании подключаемых модулей (Plug-ins),
где реализация скрыта внутри модуля.
а почему бы и нет?
Тут скорее моё субъективное предпочтение - если можно не использовать шаблоны-не используй.
тогда что будет результатом ICreator::make в случае его нешаблонности?
А мое субъективное предпочтение - использовать шаблоны там, где это поможет обобщению и повторному использованию кода. Фабрики объектов как раз таковыми и являются.
Еще есть регистрация создателя, и от выбранного типа указателя будет зависеть клиентский код. Зачем мне навязывать ту или иную библиотеку, когда я могу предоставить выбор? Я абсолютно не вижу в этом никаких граблей. Повторюсь, что использование шаблонного параметра по-умолчанию не навязывается, используй любой тип указателей. Если очень необходимо, то можно унаследоваться от этого шаблона с конкретным умным указателем и использовать его. Логика работы фабрики с указателями тривиальна - нужно обеспесить их валидность на протяжении всей жизни фабрики и все. В моем простейшем примере это обеспечивается стеком.
Сама по себе свобода выбора - это уже бонус. Или, например, нам надо посчитать сколько было обращений к каждому из создателей. Для этого достаточно изменить тип указателя на тот, в котором происходит подсчет обращений к селектору.
Идея состоит в том, что продуктом является указатель на некоторый интерфейсный класс IProduct.
Производными от него являются String и Numeric. Причем создатель может создавать не только один и тот же тип продукта,
но может и менять его в процессе выполнения программы (Mixed_creator). Поэтому пришлось частично возложить запись пакета
на сам создатель, т.к. только он имеет информацию о возможных типах продуктов. Для сериализации таких продуктов сделан специальный
сериализатор Poly_serializator, который оставляет для реализации только сериализацию идентификатора, сериализация самого продукта осуществляется
вызовом соответствующего метода создателя. Прикладываю схему примера и архив.