Смысл фабрики - производство объекта того типа который задается аргументу производящей функции. Обычно фабрики используются для производства наследников некоего абстрактного класса. Рассмотрим пример простейшей фабрики:
// base.h
class Base
{
public:
virtual void Foo() = 0;
virtual void Foo1() = 0;
virtual int Foo2(const int) = 0; // Интерфейс
static Base* make(const std::string &key); // Производящая функция
private:
Base() {} // Закрытый конструктор, для предотвращения
// создания объектов минуя производящую
// функцию
protected:
~Base() {} // Защищенный деструктор для вызова
// деструкторов потомков
};
// child1.h
#include "base.h"
class Child1 : public Base
{
public:
Child1();
~Child1();
virtual void Foo();
virtual void Foo1();
virtual int Foo2(const int);
};
// child2.h
#include "base.h"
class Child2 : public Base
{
public:
Child2();
~Child2();
virtual void Foo();
virtual void Foo1();
virtual int Foo2(const int);
};
// base.cpp
#include "base.h"
#include "child1.h"
#include "child2.h"
Base* Base::make(const std::string &key)
{
// Эта функция содержит иллюстрационный код
// Не для применения в реальных проектах!
if (key == "child1")
return new Child1();
else if (key == "child2")
return new Child2();
else
throw std::runtime_error("Неизвестный идентификатор");
}
Собственно, всё. Единственный минус нашей фабрики - функция Base::make(). Минус её в том, что при каждом добавлении потомка придется править ее код. Отличное решение этой проблемы предложил Андрей Александреску в своей книге "Современное проектирование на C++" (Modern C++ design). Суть его идеи состоит в том, чтобы вынести производящую функцию в отдельный синглтон - фабрику. Этот синглтон должен уметь запоминать ключ и указатель на функцию производящую конкретного потомка. Производящаяя функция Base::make() просто обращается к фабрике, получает от нее указатель на сконструированный объект производного класса и возвращает его.
Итак, расcмотрим фабрику Александреску:
// factory.h
#include <sstream>
#include <map>
#include <stdexcept>
class Base;
class BaseFactory
{
public:
typedef Base* (*CreateCallback)();
private:
typedef std::map<std::string, CreateCallback> CallbackMap;
public:
static BaseFactory& Instance();
bool Register(const std::string ChildKey, const CreateCallback CreateFn);
Base* Create(const std::string &ChildKey); private: CallbackMap callbacks;
BaseFactory() {}
BaseFactory(const BaseFactory&) {}
BaseFactory& operator=(const BaseFactory&) {}
~BaseFactory() {}
};
// factory.cpp
#include "factory.h"
BaseFactory& BaseFactory::Instance()
{
static BaseFactory obj; return obj;
}
bool BaseFactory::Register(const std::string ChildKey, const CreateCallback CreateFn)
{
return callbacks.insert(CallbackMap::value_type(ChildKey, CreateFn)).second;
}
Base* BaseFactory::Create(const std::string &ChildKey)
{
CallbackMap::const_iterator i = callbacks.find(ChildKey);
if (i == callbacks.end())
throw std::runtime_error("Неизвестный идентификатор");
return (i->second)();
}
Теперь мы можем переписать функцию Base::make() используя нашу фабрику:
// base.cpp
#include "base.h"
#include "factory.h"
Base* Base::make(const std::string &key)
{
return BaseFactory::Instance().Create(key);
}
И самое интересное - регистрация потомков в фабрике:
// child1.cpp
#include "factory.h"
namespace
{
Base* CreateChild1()
{
return new Child1;
}
const bool registered= BaseFactory::Instance().Register("child1", &CreateChild1);
}
// child2.cpp
... Аналогично
// child3.cpp
... Аналогично
...
Давайте проанализируем что у нас здесь происходит. Неименованный скоп (namespace {}) нужен для сокрытия
области видимости в пределах файла. CreateChild() это каллбэк для создания потомка, указатель на него
передается в фабрику, где в последствии может быть вызван. Собственно процесс регистрации выполняет
строка инициализирующая константу registered.
Вопрос применения фабрик весьма дискуссионен, однако их полезность, в общем случае, думается есть.
В качестве примера реальной иерархии использующий фабрику, я разместил в приложениях мини библиотеку для
работы с базами данных.
Ссылки по теме:
http://insidecpp.ru/patterns/factory/
Книги: