Home > Blockchain >  Factory method pattern with multiple constructors having various sets of arguments
Factory method pattern with multiple constructors having various sets of arguments

Time:11-12

I am trying to make a factory function that will be able to create objects derived from a base class using different constructors based on the given parameters. With some help from other posts here I have been able to make an example that works for a constructor that takes no parameters, but I cannot find a solution for multiple constructors.

I have the following:

#include <iostream>
#include <string>
#include <map>
#include <typeinfo>
#include <functional>

using namespace std;

class BaseObject {
public:
    BaseObject(){cout<<"BaseObject def constructor\n";};
    BaseObject(int type){cout<<"BaseObject non-def constructor\n";}
    virtual ~BaseObject() = default;
    virtual string name() = 0;
};

class Object1 : public BaseObject
{
public:
    Object1(){cout<<"Object1 def constructor\n";};
    Object1(int type){cout<<"Object1 non-def constructor\n";}
    virtual string name() override
    {
        return "I am Object1";
    } 
};

class Object2 : public BaseObject
{
public:
    Object2(){cout<<"Object2 def constructor\n";};
    Object2(int type){cout<<"Object2 non-def constructor\n";}
    virtual string name() override
    {
        return "I am Object2";
    } 
};

struct Factory {
public:
    typedef std::map<std::string, std::function<std::unique_ptr<BaseObject>()>> FactoryMap;
    
    template<class T>
    static void register_type(const std::string & name) {
        getFactoryMap()[name] = [](){ return std::make_unique<T>(); };
    }

    static std::unique_ptr<BaseObject> get_object(const std::string name) {
        return getFactoryMap()[name]();
    }
    
    static std::unique_ptr<BaseObject> get_object(const std::string name, int type) {
        return getFactoryMap()[name](type);
    }
    
    // use a singleton to prevent SIOF
    static FactoryMap& getFactoryMap() {
        static FactoryMap map;
        return map;
    }        
};


int main()
{
    Factory::register_type<Object1>("Object1");
    Factory::register_type<Object2>("Object2");

    // make Object1 using default constructor
    std::unique_ptr<BaseObject> o1 = Factory::get_object("Object1");

    // make Object2 using non-default constructor
    std::unique_ptr<BaseObject> o2 = Factory::get_object("Object2", 1);

    std::cout << o1->name() << std::endl;
    std::cout << o2->name() << std::endl;

    std::cout << "exit" << std::endl;

    return 0;
}

Both Object1 and Object2 have two constructors (it is simplified, in practice the one with the parameter will get some saved data) and Factory has two versions of get_object() each with the name of the object to be created and the corresponding additional parameters.

The problem with the second get_object

    static std::unique_ptr<BaseObject> get_object(const std::string name, int type) {
        return getFactoryMap()[name](type);
    }

is that the call to the constructor inside passes type parameter, but the type of the function (as defined by typedef FactoryMap) has no parameters (std::function<std::unique_ptr<BaseObject>()>).

I explored variadic templates but was not able to figure out how it should be done. One of the helpful post was this one, unforunately it does not have a full working code example.

CodePudding user response:

The problem is really hard in the general case as C lacks reflection. It can be solved however, in the assumption that the set of available constructors is fixed for a given factory. That is if you’re fine with defining your factory like using Factory = BaseFactory<BaseObject, void(), void(int)>; it is possible albeit requires black magic more templates. (using X = Y is the new, sane form of typedef Y X, and ret(args) is a function type [not a pointer but function itself; used like void, only in pointers and templates]). For example:

template <typename Base, typename Constructor>
struct Subfactory;

template <typename Base, typename... Args>
struct Subfactory<Base, void(Args...)> {
    using constructor_type = std::unique_ptr<Base>(Args&&...);

    template <typename Type>
    static std::unique_ptr<Base> construct(Args&&...args) {
        return std::make_unique<Type>(std::forward<Args>(args)...);
    }
};

template <typename Base, typename... Constructors>
struct BaseFactory {
public:

    using ConstructorList = std::tuple<typename Subfactory<Base, Constructors>::constructor_type *...>;

    inline static std::map<std::string, ConstructorList> types;

    template<class T>
    static void register_type(const std::string & name) {
        types[name] = ConstructorList{Subfactory<Base, Constructors>::template construct<T>...};
    }

    template <typename... Args>
    static std::unique_ptr<Base> make_object(const std::string name, Args&&...args) {
        const ConstructorList &type = types[name];
        auto constructor = std::get<std::unique_ptr<Base>(*)(Args&&...)>(type);
        return constructor(std::forward<Args>(args)...);
    }
};

using Factory = BaseFactory<BaseObject, void(), void(int)>;

int main()
{
    Factory::register_type<Object1>("Object1");
    Factory::register_type<Object2>("Object2");

    // make Object1 using default constructor
    std::unique_ptr<BaseObject> o1 = Factory::make_object("Object1");

    // make Object2 using non-default constructor
    std::unique_ptr<BaseObject> o2 = Factory::make_object("Object2", 1);

    std::cout << o1->name() << std::endl;
    std::cout << o2->name() << std::endl;

    std::cout << "exit" << std::endl;

    return 0;
}

Explanation

template <typename Base, typename Constructor>
struct Subfactory;

template <typename Base, typename... Args>
struct Subfactory<Base, void(Args...)> {
    using constructor_type = std::unique_ptr<Base>(Args&&...);

    template <typename Type>
    static std::unique_ptr<Base> construct(Args&&...args) {
        return std::make_unique<Type>(std::forward<Args>(args)...);
    }
};

This is a helper to unpack a function type. Specifically, the partial specialization matches all usages of the form Subfactory<any type, void(anything here)>, filling Args... with that “anything”.

Now, the factory itself.

template <typename Base, typename... Constructors>
struct BaseFactory {

Here, Constructors... is for the list of constructor signatures

    using ConstructorList = std::tuple<typename Subfactory<Base, Constructors>::constructor_type *...>;

For each element C of Constructors..., this extracts the type Subfactory<Base, C>::constructor_type *, and defines ConstructorList as a tuple of all these types.

    inline static std::map<std::string, ConstructorList> types;

A nice, C 17-only (but note that make_unique is C 17 too) replacement for getFactoryMap. Optional, your getFactoryMap is equally usable.

    template<class T>
    static void register_type(const std::string & name) {
        types[name] = ConstructorList{Subfactory<Base, Constructors>::template construct<T>...};

This instantiates Subfactory<Base, C>::construct<T> for each C from Constructors... and makes ConstructorList passing (pointers to) these functions as arguments.

    template <typename... Args>
    static std::unique_ptr<Base> make_object(const std::string name, Args&&...args) {
        const ConstructorList &type = types[name];
        auto constructor = std::get<std::unique_ptr<Base>(*)(Args&&...)>(type);
        return constructor(std::forward<Args>(args)...);

This gets the “type info” (constructor list) from the map, then gets appropriate constructor [wrapper] from the tuple (based on the received arguments), and calls it.

using Factory = BaseFactory<BaseObject, void(), void(int)>;

Here, Factory is defined as a factory of BaseObject supporting constructors with no arguments and with single int argument.

Note that this solution is not perfect. It requires good match of arguments given to the factory with the arguments it supports; no overloading resolution takes place. Supporting that is likely possible but requires more complicated tricks.

Update: here is a similar solution but with proper overload resolution:

/// A wrapper over single constructor of a single type.
/// @param Base is the common type
/// @param Constructor is a function type denoting the constructor signature. It must have the form `void(constructor arguments)`
template <typename Base, typename Constructor>
struct Subfactory;

/// The only specialization of @c Subfactory
template <typename Base, typename... Args>
struct Subfactory<Base, void(Args...)> {
    /// The pointer to the constructor wrapper.
    std::unique_ptr<Base> (*constructor)(Args&&...args);

    /// The outer constructor wrapper. Unlike @c constructor which is a variable, this one is a function thus can participate in overload resolution.
    std::unique_ptr<Base> construct(Args&&...args) {
        return constructor(std::forward<Args>(args)...);
    }

    /// A factory factory. Returns an instance able to construct an object of type @p Type, but only with a constructor accepting @p Args as arguments.
    template <typename Type>
    static Subfactory metafactory() {
        /// The constructor wrapper (written as a lambda for compactness)
        return {[](Args&&...args) -> std::unique_ptr<Base> {
            return std::make_unique<Type>(std::forward<Args>(args)...);
        }};
    }
};

/// The generic factory.
/// @param Base is the common type. Objects are returned as pointers to that type
/// @param Constructors are function types denoting the constructor signatures. Each must have the form `void(constructor arguments)`, and they must all be distinct
template <typename Base, typename... Constructors>
struct BaseFactory {
public:
    /// A wrapper on constructor list of a single type.
    /// It inherits one @c Subfactory for each constructor signature from @c Constructors.
    /// Could also hold additional information, if necessary.
    struct TypeInfo: public Subfactory<Base, Constructors>...
    {
        /// Another factory factory. Returns an instance able to construct an object of type @p Type with any supported constructor.
        template <typename Type>
        static TypeInfo metafactory() {
            return TypeInfo{
                Subfactory<Base, Constructors>::template metafactory<Type>()...
            };
        }

        /// Brings *all* constructor wrappers in the scope so that @c construct names them all, as overloaded functions.
        using Subfactory<Base, Constructors>::construct...;
    };

    inline static std::map<std::string, TypeInfo> types;

    template<class Type>
    static void register_type(const std::string & name) {
        types[name] = TypeInfo::template metafactory<Type>();
    }

    template <typename... Args>
    static std::unique_ptr<Base> make_object(const std::string name, Args&&...args) {
        return types[name].construct(std::forward<Args>(args)...);
    }
};

/// A factory of @c BaseObject subclasses, supporting constructors taking nothing or a single int.
using Factory = BaseFactory<BaseObject, void(), void(int)>;

int main()
{
    Factory::register_type<Object1>("Object1");
    Factory::register_type<Object2>("Object2");

    // make Object1 using default constructor
    std::unique_ptr<BaseObject> o1 = Factory::make_object("Object1");

    // make Object2 using non-default constructor
    std::unique_ptr<BaseObject> o2 = Factory::make_object("Object2", 1);

    // make Object2 using overload resolution of a non-default constructor
    std::unique_ptr<BaseObject> o3 = Factory::make_object("Object2", 'c');

    std::cout << o1->name() << std::endl;
    std::cout << o2->name() << std::endl;
    std::cout << o3->name() << std::endl;

    std::cout << "exit" << std::endl;

    return 0;
}

Instead of storing function pointers in a tuple, a special type, TypeInfo, is used. One pointer is stored in each its base class, all of which are Subfactory but with different template arguments. Each Subfactory defines a construct function with appropriate arguments, and TypeInfo inherits them all and makes them all visible, thus subject to overload resolution like original constructors themselves.

  • Related