25 марта 2006

Object Factory

Задача
В проекте, над которым я сейчас работаю, постоянно стали требоваться так называемые "фабрики объектов" - глобальные объекты, создающие экземпляры каких-либо классов по указанию определенного идентификатора. Например, бизнес-логика, генерирующая отчеты. На входе имеем ID отчета, который нужно создать, и параметры отчета - на выходе нужно получить сформированный отчет. Причем в виде switch (ReportID) { ... } это реализовывать неудобно - по мере добавления отчетов будет расти количество case и все они собраны в одном месте, то есть нужно каждый раз перекомпилировать этот switch... Идеальное решение, чтобы можно было написать реализацию определенного отчета в отдельном модуле (скажем, файле исходного текста), и чтобы он сам "зарегистрировался" в фабрике отчетов под своим ID. Тогда запросив у фабрики отчет по ID, она сама найдет "создателя" нужного отчета.

Решение
Я попытался общий создать шаблон для подобных фабрик объектов. В частном случае от фабрики требуется вызвать функцию, зарегистрированную для определенного ID типа IdType. Функция принимает в качестве параметра объект типа ParameterType, а возвращает значение типа ReturnType.

Например, для отчетов может быть IdType = string, ParameterType = map<string, _variant_t>, ReturnType = shared_ptr<Report>. То есть отчет идентифицируем по его названию, передаем ему параметры, где каждый параметр = имя (string) + значение (_variant_t), а получаем – умный указатель на созданный отчет.

Шаблон я создал следующий: Factory.h

Пример использования:
typedef CFactory
<const string &, shared_ptr<Report>, const map<string, _variant_t> & >
CReportFactory;

shared_ptr<report> Generator(const map<string, _variant_t> &)
{
// generator code
}

// register Generator as "test report"
bool Registered = CReportFactory::Register("test report", Generator);

int main()
{
// fill parameters
map<string, _variant_t> Parameters;
Parameters.insert(...);

// generate report
shared_ptr<Report> TestReport =
CReportFactory::Execute("test report", Parameters);
}

Нюансы
В случае, если все генераторы отчетов компилируются все в одном месте с самой фабрикой (то есть не разнесены по разным библиотекам), то регистрация на фабрике выполняется достаточно просто – введением глобальной переменной:
bool SomeGeneratorRegistered = SomeFactory.Register(SomeID, SomeGenerator);

В случае, если такую конструкцию разместить в отдельной библиотеке, то ликновщик не прилинкует ее, так как на нее не будет явных ссылок. В тако случае приходится прописывать явно:

В библиотеке:
bool SomeLibrary1Registered =
SomeGenerator11Registered &&
SomeGenerator12Registered &&
...;

В основном модуле (исполнимый файл или DLL):
extern bool SomeLibrary1Registered;
extern bool SomeLibrary2Registered;
...
bool LibrariesRegistered =
SomeLibrary1Registered &&
SomeLibrary2Registered &&
...;

Комментариев нет: