Решение для типовой задачи, когда есть интерфейс, реализации интерфейса, и реализации создаются фабрикой по некоторому значению (типу реализации). В решении обеспечивается саморегистрация классов реализаций интерфейса в фабрике, т.о. отпадает необходимость в switch'е в фабрике, и решение становиться максимально открытым для расширения.
Может кому будет полезно. Или кто выскажет конструктивные замечания.
// Некий интерфейс, реализации которого будем создавать с помощью фабрикиstruct IHeader
{
typedef boost::shared_ptr<IHeader> Ptr;
virtual ~IHeader() = 0 {};
};
// Интерфейс метакласса реализации IHeaderstruct IHeaderWrapper
{
typedef boost::shared_ptr<IHeaderWrapper> Ptr;
virtual ~IHeaderWrapper() = 0 {};
virtual IHeader::Ptr create() = 0;
};
// Фабрика реализаций IHeaderclass HeaderFactory
{
private:
typedef std::map<int, IHeaderWrapper::Ptr> map;
static map& wrappers()
{
static map wrappers_instance;
return wrappers_instance;
}
public:
// Метод создания экземпляраstatic IHeader::Ptr create(int type)
{
map::iterator iter = wrappers().find(type);
if (wrappers().end() != iter)
return iter->second->create();
else
throw std::runtime_error("unknown header type");
}
// Метод добавления нового типа экземпляраstatic void add(int type, IHeaderWrapper::Ptr wrapper)
{
if (!wrappers().insert(std::make_pair(type, wrapper)).second)
throw std::logic_error("Header type added twice");
}
};
// Метакласс реализации IHeadertemplate<typename HeaderType>
struct HeaderWrapper : IHeaderWrapper
{
virtual IHeader::Ptr create()
{
return IHeader::Ptr(new HeaderType);
}
};
// Базовый класс для реализаций IHeader - обеспечивает саморегистрацию реализаций в фабрикеtemplate<typename Derived>
struct Header : IHeader
{
private:
struct Initializer
{
Initializer()
{
// Саморегистрация
HeaderFactory::add(Derived::TYPE, IHeaderWrapper::Ptr(new HeaderWrapper<Derived>));
}
};
static Initializer initializer;
protected:
~Header()
{
// Надо обязательно заюзать эту переменную
(void)initializer;
}
};
template<typename HeaderType> typename Header<HeaderType>::Initializer Header<HeaderType>::initializer;
// Реализация IHeaderstruct Header1 : Header<Header1>
{
static const int TYPE = 1;
// Реализация
};
// Реализация IHeaderstruct Header2 : Header<Header2>
{
static const int TYPE = 2;
// Реализация
};
// Реализация IHeaderstruct Header3 : Header<Header3>
{
static const int TYPE = 3;
// Реализация
};
int main()
{
IHeader::Ptr h1 = HeaderFactory::create(1);
IHeader::Ptr h2 = HeaderFactory::create(2);
IHeader::Ptr h3 = HeaderFactory::create(3);
IHeader::Ptr h4 = HeaderFactory::create(4); // Тут будет исключение
}
В принципе тут можно уменьшить кол-во классов путём совмещения: например код из HeaderFactory можно поместить в IHeader. Тут так сказать всё разложено по полочкам.
Заполнение фабрики информацией о реализациях происходит во время инициализации глобальных объектов. Т.е. уже к main() она "константная".
Сразу предвижу замечания по поводу оверхеда. Возможно он есть тут. Конечно для тривиальных случаев делать так не стоит. Но для сложных, я считаю такое решение оправдано. Зато это создаёт некую инфраструктуру, с которой "пользовательские классы" становяться проще. Зато тут всё ортогонально и все классы и методы простые, короткие и понятные. А с "более простыми решения" рука об руку идут switch'и, дублирование и другие прелести.
Плюс эту инфрастуктуру можно нагрузить ещё всякими дополнительными бонусами.
1. Например, в фабрику можно поместить метод:
// Фабрика реализаций IHeaderclass HeaderFactory
{
public:
// Проверка на существование типаstatic bool exists(int type)
{
return wrappers().end() != wrappers().find(type)
}
};
Например, загрузили из БД тип (число), сразу можно проверить, а правилное ли это число, есть ли у нас такой тип.
2. С каждой реализацией можно связать гораздо больше информации, чем просто тип
// Реализация IHeaderstruct Header1 : Header<Header1>
{
// Связываем с типом числовое значениеstatic const int TYPE = 1;
// Связываем с типом другой тип. Например, тип диалога, который может отобразить информацию о данной реализацииtypedef Header1Dlg DlgType;
// Связываем с типом некий флагstatic const bool IS_SIMPLE = true;
// Связываем с типом строку, которую мы можем выводить пользователюstatic const char* GetName()
{
return"SOME_HEADER";
}
// Реализация
};
Соответственно надо расширить IHeaderWrapper для поддержки этих значений.
3. В фабрику можно добавить метод для получения всех типов:
// Фабрика реализаций IHeaderclass HeaderFactory
{
public:
// Получить список всех метаклассовstatic std::vector<IHeaderWrapper::Ptr> getAllWrappers();
};
Имея всё это можно написать функцию, например, для заполнения комбобокса информацией о всех существующих типах + с каждым элементом комбобокса связать код этого типа.
Или функцию, которая выберет из всех типов типы, которые отвечают заданному критерию, например, у которых флаг IS_SIMPLE == true;
В общем можно много чего ещё полезного реализовать, причём реализовать так сказать "красиво".
Плюсы:
1. Практически нулевое дублирование, что важно для сопровождаемого кода
2. Инрастуктура функциональная, расширяемая и "умная"
3. Сами классы реализаций интерфейсов абсолютно самодостаточные, самоописываемые, т.е. высокая локальность данных и функций
4. Чтобы добавить новый класс реализации надо просто его описать и его поддержка автоматически появиться везде где она должна быть. Чем не может похвастаться традиционный подход — саму реализацию добавил, добавил её поддержку в фабрику, добавил её поддержку в интерфейс пользователя, а при загрузке из БД забыл.
Здравствуйте, remark, Вы писали:
R>Решение для типовой задачи, когда есть интерфейс, реализации интерфейса, и реализации создаются фабрикой по некоторому значению (типу реализации). В решении обеспечивается саморегистрация классов реализаций интерфейса в фабрике, т.о. отпадает необходимость в switch'е в фабрике, и решение становиться максимально открытым для расширения. R>Может кому будет полезно. Или кто выскажет конструктивные замечания.
См. Александреску "Современное проектирование...". ИМХО там реализация лучше. А в последних версиях Loki аффтар "творчески переработал" этот кусок.
Здравствуйте, DigitalGuru, Вы писали:
DG>Здравствуйте, remark, Вы писали:
R>>Может кому будет полезно. Или кто выскажет конструктивные замечания.
DG>Так и хочется написать: "Ниасилил, слишком многа классав"
Здравствуйте, remark, Вы писали:
R>Решение для типовой задачи, когда есть интерфейс, реализации интерфейса, и реализации создаются фабрикой по некоторому значению (типу реализации). В решении обеспечивается саморегистрация классов реализаций интерфейса в фабрике, т.о. отпадает необходимость в switch'е в фабрике, и решение становиться максимально открытым для расширения. R>Может кому будет полезно. Или кто выскажет конструктивные замечания.
R>
// <skip>
R>[ccode]
R>// Реализация IHeader
R>struct Header1 : Header<Header1>
R>{
R> // Связываем с типом числовое значение
R> static const int TYPE = 1;
R> // Связываем с типом другой тип. Например, тип диалога, который может отобразить информацию о данной реализации
R> typedef Header1Dlg DlgType;
R> // Связываем с типом некий флаг
R> static const bool IS_SIMPLE = true;
R> // Связываем с типом строку, которую мы можем выводить пользователю
R> static const char* GetName()
R> {
R> return"SOME_HEADER";
R> }
R> // Реализация
R>};
R>
Можно передавать через шаблон некоторые аргументы:
Бонус небольшой изврат :-D
R>2. Инрастуктура функциональная, расширяемая и "умная"
Пока не будут применятся в полную метапрограммирование на шаблонах и на макросах не будет полностью расширяемая R>3. Сами классы реализаций интерфейсов абсолютно самодостаточные, самоописываемые, т.е. высокая локальность данных и функций R>4. Чтобы добавить новый класс реализации надо просто его описать и его поддержка автоматически появиться везде где она должна быть. Чем не может похвастаться традиционный подход — саму реализацию добавил, добавил её поддержку в фабрику, добавил её поддержку в интерфейс пользователя, а при загрузке из БД забыл.
R>... что-то уже до целой статьи дотягивает
Приветствуется
R>
"remark" <38267@users.rsdn.ru> wrote in message news:1788774@news.rsdn.ru... > Решение для типовой задачи, когда есть интерфейс, реализации интерфейса, и реализации создаются фабрикой по некоторому значению (типу реализации). В решении обеспечивается саморегистрация классов реализаций интерфейса в фабрике, т.о. отпадает необходимость в switch'е в фабрике, и решение становиться максимально открытым для расширения. > Может кому будет полезно. Или кто выскажет конструктивные замечания. >...
А что, мне нравится.
Конечно слабым местом является то, что идентификаторы нужно назначать в каждом типе руками и, похоже, применительно к тому примеру, от которого родился этот топик, от этого никуда не денешься: в потоке данных хранятся именно числа. А вот в общем случае, ключ мапа можно было бы сделать строковым и загонять туда typeid(Derived).name(), вот это вообще будет хорошее решение.
Posted via RSDN NNTP Server 1.9
--
Справедливость выше закона. А человечность выше справедливости.
R>"remark" <38267@users.rsdn.ru> wrote in message news:1788774@news.rsdn.ru... >> Решение для типовой задачи, когда есть интерфейс, реализации интерфейса, и реализации создаются фабрикой по некоторому значению (типу реализации). В решении обеспечивается саморегистрация классов реализаций интерфейса в фабрике, т.о. отпадает необходимость в switch'е в фабрике, и решение становиться максимально открытым для расширения. >> Может кому будет полезно. Или кто выскажет конструктивные замечания. >>...
R>А что, мне нравится. R>Конечно слабым местом является то, что идентификаторы нужно назначать в каждом типе руками и, похоже, применительно к тому примеру, от которого родился этот топик, от этого никуда не денешься: в потоке данных хранятся именно числа. А вот в общем случае, ключ мапа можно было бы сделать строковым и загонять туда typeid(Derived).name(), вот это вообще будет хорошее решение.
Да, но это уже зависит от конкретных условий приложения. Если в приложении подгружаются dll-модули, то выражение & typeid(Derived) , скрее всего, будет иметь разное значение в разных модулях. И если задача стоит о сохранении абстрактных данных в поток(файл) с последующей загрузкой этих данных, возможно даже другим приложением, то тоже не пройдет такой подход. Со строкой, конечно же, медленнее, но зато круг решаемых задач шире.
Posted via RSDN NNTP Server 1.9
--
Справедливость выше закона. А человечность выше справедливости.
Re: Саморегистрация классов в фабрике
От:
Аноним
Дата:
17.03.06 13:33
Оценка:
Так это, с чего всё начиналось —
да, switch'а нет, зато есть std::map, что в плане производительности ещё хуже
Здравствуйте, remark, Вы писали:
R>Надеюсь хоть кто-то дочитает до конца
Дочитал... Полезное решение..
Вот только есть одна проблема, о которой стоит, наверно, вспомнить...
При реализации классов в статической библиотеке существует вероятность, что они будут таки проигнорированы линкером....
Идея авторегестрации — ссылка на конструктор статического объекта в деструкторе?
Как это объясняет стандарт?
R>"remark" А вот в общем случае, ключ мапа можно было бы сделать строковым и загонять туда typeid(Derived).name(), вот это вообще будет хорошее решение.
hash(typeid(Derived).name())
будет хорошим решением (реализацию hash для string взять из java)
Здравствуйте, Аноним, Вы писали:
А>Здравствуйте, remark, Вы писали:
R>>Решение для типовой задачи, когда есть интерфейс, реализации интерфейса, и реализации создаются фабрикой по некоторому значению (типу реализации). В решении обеспечивается саморегистрация классов реализаций интерфейса в фабрике, т.о. отпадает необходимость в switch'е в фабрике, и решение становиться максимально открытым для расширения. R>>Может кому будет полезно. Или кто выскажет конструктивные замечания.
А>См. Александреску "Современное проектирование...". ИМХО там реализация лучше. А в последних версиях Loki аффтар "творчески переработал" этот кусок.
Нет, там другое.
Часть функциональности, о которой я говорю там есть. Но только часть, причём не главная. Там нет саморегистрации. Там нет возможности нагружать эту инфроструктуру дополнительной функциоанльностью. То о чём я говорю — своего рода метаинформация или рефлекшн — и соответственно код на более высоком уровне.
Хотя, возможно, мой класс фабрики можно относледовать от класса Loki::Factory, что бы заюзать часть функциональности.
Здравствуйте, BitField, Вы писали:
BF>Здравствуйте, remark, Вы писали:
R>>Надеюсь хоть кто-то дочитает до конца
BF>Дочитал... Полезное решение..
BF>Вот только есть одна проблема, о которой стоит, наверно, вспомнить... BF>При реализации классов в статической библиотеке существует вероятность, что они будут таки проигнорированы линкером....
Можно поподробнее, о каких именно классах идёт речь?
Когда я использовал такое решение идентификаторы хранились или в БД или были связаны с внешними программами. В таком случае явное указание идентификатора не только можно, но и нужно.
Если идентификаторы используются только внутри программы и за её пределы ни во времни, ни в пространстве не выходят, но можно сделать автоикремент. По-моему самое логичное для него место — при регистрации в фабрике. Если они конечно не нужны в компайл-тайм.
Хотя нужны ли будут в таком случае вообще идентификаторы и фабрика? Надо смотреть.
R>>2. Инрастуктура функциональная, расширяемая и "умная" __>Пока не будут применятся в полную метапрограммирование на шаблонах и на макросах не будет полностью расширяемая
Ну шаблоны тут есть (Header<>, HeaderWrapper<>), в них можно помещать шаблонный код.
Для макросов пока как-то не нашлось применения
R>>... что-то уже до целой статьи дотягивает __>Приветствуется
Лень
Здравствуйте, Аноним, Вы писали:
А>Так это, с чего всё начиналось - А>да, switch'а нет, зато есть std::map, что в плане производительности ещё хуже
Это не совсем с того началось, о чём вы думаете. Это началось с вопроса о саморегистрации.
А если нужна высокая производительность в этом месте, то в фабрике можно использовать упорядоченный vector или сделать тот же switch.
А если у интерфейса IHeader есть несколько виртуальных методов, то это решение будет выигрывать по скорости у С-подхода, т.к. switch (или поиск в упорядоченном vector) будет выполняться только один раз, а не много раз.
R>"remark" <38267@users.rsdn.ru> wrote in message news:1788774@news.rsdn.ru... >> Решение для типовой задачи, когда есть интерфейс, реализации интерфейса, и реализации создаются фабрикой по некоторому значению (типу реализации). В решении обеспечивается саморегистрация классов реализаций интерфейса в фабрике, т.о. отпадает необходимость в switch'е в фабрике, и решение становиться максимально открытым для расширения. >> Может кому будет полезно. Или кто выскажет конструктивные замечания. >>...
R>А что, мне нравится. R>Конечно слабым местом является то, что идентификаторы нужно назначать в каждом типе руками и, похоже, применительно к тому примеру, от которого родился этот топик, от этого никуда не денешься: в потоке данных хранятся именно числа. А вот в общем случае, ключ мапа можно было бы сделать строковым и загонять туда typeid(Derived).name(), вот это вообще будет хорошее решение.
Не думаю, что в общем случае это будет хорошее решение. Там уже ниже такие замуты пошли про typeid и hash...
По моему опыту обычно идентификаторы либо храняться в БД, либо в файле, либо приходят по сети, либо это названия элементов в XML. Т.е. явное указание идентификатора не минус, а плюс.
Явно его можно не указывать только в том случае, если он не выходит за пределы программы не в пространстве, ни во времени. Тогда стоит вопрос, а нужна ли будет в таком случае вообще фабрика и идентификаторы?
Обычна фабрика нужна, когда тип класса считывают из внешнего источника: БД/XML/socket. А если всё происходит внутри программы, то зачем нужна фабрика и идентификаторы типов???
Здравствуйте, remark, Вы писали:
R>Здравствуйте, _nn_, Вы писали:
__>>Можно передавать через шаблон некоторые аргументы: __>>Бонус небольшой изврат :-D __>>
R>Можно. Тут по вкусу. Принипиально ничего не поменяется. R>Имхо так более наглядно и явно: R>
R> // Связываем с типом числовое значение
R> static const int TYPE = 1;
R>
Возможно лучше будет функция.
Так можно будет во времени выполнения загружать идентификатор.
__>>Что насчет автоинкремента номера ? __>>Что-то вроде: __>>
R>Когда я использовал такое решение идентификаторы хранились или в БД или были связаны с внешними программами. В таком случае явное указание идентификатора не только можно, но и нужно. R>Если идентификаторы используются только внутри программы и за её пределы ни во времни, ни в пространстве не выходят, но можно сделать автоикремент. По-моему самое логичное для него место — при регистрации в фабрике. Если они конечно не нужны в компайл-тайм.
Значит нужно какое-то универсальное и красивое решение R>Хотя нужны ли будут в таком случае вообще идентификаторы и фабрика? Надо смотреть.
А как определять разные классы ? По typeid ?
Если можно то лучше без него
R>>>2. Инрастуктура функциональная, расширяемая и "умная" __>>Пока не будут применятся в полную метапрограммирование на шаблонах и на макросах не будет полностью расширяемая R>Ну шаблоны тут есть (Header<>, HeaderWrapper<>), в них можно помещать шаблонный код. R>Для макросов пока как-то не нашлось применения
Значит разработка еще не доросла до библиотечного уровня
R>>>... что-то уже до целой статьи дотягивает __>>Приветствуется R>Лень
Не аргумент
R>>> R>
Здравствуйте, remark, Вы писали:
R>Здравствуйте, BitField, Вы писали:
BF>>Вот только есть одна проблема, о которой стоит, наверно, вспомнить... BF>>При реализации классов в статической библиотеке существует вероятность, что они будут таки проигнорированы линкером....
R>Можно поподробнее, о каких именно классах идёт речь?
Фабрика и классы заголовков (Header1, Header2) находятся в статической библиотеке. Пусть, например, Header1 (все его ф-ции, vtbl, и регистратор) находятся в единице трансляции, после компиляции которой имеет Header1.o[bj]. Он помещается в статическую библиотеку. При линковке этой библиотеки к исполняемому файлу в случае, если ни один из символов, определенных в Header1.o[bj], не используется в других обьектных файлах, линкер имеет полное право игнорировать Header1.o[bj] целиком -- и саморегистрация Header1 не происходит.
Здравствуйте, remark, Вы писали:
R>Здравствуйте, Аноним, Вы писали:
А>>Здравствуйте, remark, Вы писали:
R>>>Решение для типовой задачи, когда есть интерфейс, реализации интерфейса, и реализации создаются фабрикой по некоторому значению (типу реализации). В решении обеспечивается саморегистрация классов реализаций интерфейса в фабрике, т.о. отпадает необходимость в switch'е в фабрике, и решение становиться максимально открытым для расширения. R>>>Может кому будет полезно. Или кто выскажет конструктивные замечания.
А>>См. Александреску "Современное проектирование...". ИМХО там реализация лучше. А в последних версиях Loki аффтар "творчески переработал" этот кусок.
R>Нет, там другое. R>Часть функциональности, о которой я говорю там есть. Но только часть, причём не главная. Там нет саморегистрации.
ИМХО, как раз основое (оно же главное) там есть. Добавлена только саморегистрация, что есть довольно сомнительное приобретение. В "классической" реализации фабрики классов есть возможность собрать вызовы Register в одном месте, что очень удобно при большом количестве "продуктов". В твоём же случае, когда тебе понадобиться добавить очередной продукт, придётся обшарить все модули с целью узнать следующее незанятое значение TYPE. А их может быть несколько больше, чем один. R>Там нет возможности нагружать эту инфроструктуру дополнительной функциоанльностью. То о чём я говорю — своего рода метаинформация или рефлекшн — и соответственно код на более высоком уровне.
Вот тут хотелось бы поподробнее. Смысл фабрики — сокрытие типов. Откуда возьмётся информация?
IHeader::Ptr h1 = HeaderFactory::create(48);
h1 ... что дальше? IS_SIMPLE? как?
R>Хотя, возможно, мой класс фабрики можно относледовать от класса Loki::Factory, что бы заюзать часть функциональности. ))))
R>Плюсы: R>1. Практически нулевое дублирование, что важно для сопровождаемого кода R>2. Инрастуктура функциональная, расширяемая и "умная"
Loki::Factory R>3. Сами классы реализаций интерфейсов абсолютно самодостаточные, самоописываемые, т.е. высокая локальность данных и R>функций
Это уж как их реализует пользователь этой фабрики. R>4. Чтобы добавить новый класс реализации надо просто его описать и его поддержка автоматически появиться везде где R>она должна быть. Чем не может похвастаться традиционный подход — саму реализацию добавил, добавил её поддержку в R>фабрику, добавил её поддержку в интерфейс пользователя, а при загрузке из БД забыл.
Про обратную сторону медали этого удобства я уже писал.
Резюмируя: к Loki::Factory добавлено две примочки, одна из которых может быть полезна при условии, что реализация продуктов сконцентрирована в очень небольшом числе модулей, а вторая просто бессмыслена.
Здравствуйте, Аноним, Вы писали:
А>Так это, с чего всё начиналось - А>да, switch'а нет, зато есть std::map, что в плане производительности ещё хуже
Ты, другой аноним, нашего ремарка не обижай.
Какая тебе производительность?
Библиотеки пишут для увеличения скорости разработки.
Лучше нарисуй-ка мне свой быстрый свитч для неинтегрального типа. Хоть для строки.
P.S. А уменя смайлики отвалились — вставляться не хочут 8(