In this tip, you will learn how to automatically map some key to the fabric function, and how to use it.
Introduction
There are a lot of situations when we should create an instance of the class but the code where this situation arises doesn't know exactly the type of object to create, the only key is known.
For example, we are deserializing the file, which stored some document and encounters a record with the object of a class with the name OrangeCar
, thus to read it, we may write something like:
class Car{};
class OrangeCar : public Car{};
class RedCar : public Car{};
Document read(std::istream in)
{
Document doc;
std::unique_ptr<Car> car;
std::string className = ...;
if (className == "OrangeCar")
{
car = std::make_unique<OrangeCar>();
}
else if (className == "RedCar")
{
car = std::make_unique<RedCar>();
}
.....
else if (className == "CarOf2058year")
{
car = std::make_unique<CarOf2058year>();
}
car->read(in); doc.cars.push_back(car);
}
The new car will be added in 2058 and 2059 and later, thus we should rework this function every time when new car arises. It would be better to have something like:
Document read(std::istream in)
{
Document doc;
std::unique_ptr<Car> car;
std::string className = ...;
auto car = CarFactory::createObject(className);
car->read(in); doc.cars.push_back(car);
}
Let's build such magic CarFactory
!
It would be great to decrease overhead then we want to add a new car
class to CarFactory
, something like that would be enough:
class NewCar : public Car {};
CarFactory::add<NewCar>("NewCar");
But this function would never be called, to call it, we should initialize some static or global variable, thus rework:
auto NewCarrAdded = CarFactory::add<NewCar>("NewCar");
The NewCarrAdded
type and size don't play matter at all! The compiler should call CarFactory::add<NewCar>("NewCar");
Thus one of the CarFactory
functions are known:
class CarFactory
{
public:
struct Added {}; template<typename NewCar>
static Added add(std::string key);
static std::unique_ptr<Car> createObject(std::string key);
};
To make this class more general, one should remove dependencies from class Car
and registration key type:
template<typename Key, typename ObjectBase>
class Factory
{
public:
using KeyType = Key;
using ObjectBase = ObjectBaseType;
using ObjectBasePtr = std::unique_ptr<ObjectBaseType>;
struct Added {}; template<typename Object>
static Added add(KeyType key);
static ObjectBasePtr createObject(const KeyType& key);
};
Usage of this class is very simple:
- Somewhere one should describe concrete factory:
using CarFactory = Factory<std::string, Car>;
- Add every object inherited from
Car
to CarFactory
:
auto NewCarrAdded = CarFactory::add<NewCar>("NewCar");
- Create
car
s:
auto car = CarFactory::createObject("NewCar");
Implementation
Who will create objects? Some function like:
template <typename Object>
std::unique_ptr<Object> = createObject() { return std::make_unique<Object>; }
Thus we need a map that matches the key to functions:
template <typename Object>
using Function = std::unique_ptr<Object>(*)();
template <typename Key, typename Function>
using Map = std::unordered_map<Key, Function>;
So the Factory
became:
template<typename Key, typename ObjectBase>
class Factory
{
public:
using KeyType = Key;
using ObjectBase = ObjectBaseType;
using ObjectBasePtr = std::unique_ptr<ObjectBaseType>;
using CreatorFunction = std::function<ObjectBasePtr()>;
class Factories
{
public:
using Map = std::unordered_map<KeyType, CreatorFunction>;
void addCreator(Key key, CreatorFunction function)
{
m_factories[std::move(key)] = function;
}
CreatorFunction getCreatorFunction(const Key& key) const
{
if (auto found = m_factories.find(key); found != m_factories.end())
return found->second;
return nullptr;
}
const Map& getCreatorFunctions() const
{
return m_factories;
}
private:
Map m_factories;
};
struct Added {}; template<typename Object>
static Added add(KeyType key)
{
auto& factories = getFactories();
static_assert(std::is_base_of<ObjectBase, Object>::value,
"Object must inherit ObjectBase");
static auto makeObject = [](){ return std::make_unique<Object>(); };
factories.addCreator(std::move(key), makeObject);
}
static ObjectBasePtr createObject(const KeyType& key)
{
auto& factories = getFactories();
if (auto function = factories.getCreatorFunction(key))
{
return function();
}
return {};
}
static typename Base::Factories& getFactories()
{
static typename Base::Factories factories;
return factories;
}
};
History
- 2nd June, 2021: Initial version