c++abstract-factory

C++ Perfect Generic Abstract Factory with arbitrary Constructor Arguments


for unit testing, I'm trying to create a Factory which meets the following requirements:

The purpose is to do dependency injection with this factories to be able to exchange objects that are not part of the test with mock objects.

Here's what I have so far:

#include <memory>
#include <iostream>

//"perfect factory method"
//usage: std::unique_ptr<Type> pt(create<Type>(Type-constructor-arguments));
template <typename Type, typename ... ConstructorArgs>
auto create(ConstructorArgs&& ... args){
    return std::make_unique<Type>(std::forward<ConstructorArgs>(args)...);
}

//Abstract Factory Base class
template<typename BaseType, typename ... ConstructorArgs>
class IFactory {
    public:
        virtual ~IFactory() = default;
        virtual std::unique_ptr<BaseType> create(ConstructorArgs&& ... args) const = 0;
};

//Abstract Factory class
template <typename BaseType, typename DerivedType, typename ... ConstructorArgs>
class CFactory : public IFactory<BaseType, ConstructorArgs...>
{
    public:
        using Base = IFactory<BaseType, ConstructorArgs...>;
        std::unique_ptr<BaseType> create(ConstructorArgs&& ... args) const override
        {
            return ::create<DerivedType>(std::forward<ConstructorArgs>(args)...);
        }
};

How the real Factory classes are defined:

class TimerInterface {
    public:
        TimerInterface() = default;
        TimerInterface (const char* name);
        virtual void whoami() const = 0;
        /*...*/
};

class Timer: public TimerInterface {
    public:
        Timer() = default;
        Timer(const char* name) : TimerInterface (name) {}
        void whoami() const override { std::cerr << "I'm real!" << std::endl; }
        /*...*/
};


class TimerMock : public TimerInterface {
    public:
        TimerMock () = default;
        TimerMock (const char* name) : TimerInterface (name) {}
        void whoami() const override { std::cerr << "I fake it!" << std::endl; }
        /*...*/
};

using TimerFactory = CFactory<TimerInterface, Timer, const char*>;
using TimerMockFactory = CFactory<TimerInterface, TimerMock, const char*>;

using TimerFactoryInterface = TimerFactory::Base;

And how they are intended to be used:

class ClassUnderTest {
    public:
        std::unique_ptr<TimerInterface> timer {};
        std::unique_ptr<TimerInterface> timer2 {};
    
        ClassUnderTest(const TimerFactoryInterface& factory)
        : timer(factory.create("I got a name!"))
        //, timer2(factory.create())
        {}
};

class Production
{
    public:
        ClassUnderTest realUsage;
        
        Production() :
        realUsage(TimerFactory())
        {}
};

class Test
{
    public:
        ClassUnderTest tested;
        
        Test() :
        tested(TimerMockFactory())
        {}  
};

int main()
{
    Production p;
    p.realUsage.timer->whoami();
    
    Test t;
    t.tested.timer->whoami();
}

My big problem is requirement (4) ClassUnderTest::timer2 can't be created with the same factory that's used for ClassUnderTest::timer, beause the constructor signatur already needs to be known when defining the CFactory class.

Anyone got an idea?

P.S.: "It can't be done" with an explanation is also an acceptable answer, but not my favourite ;)


Solution

  • @SparkyPotato gave me the idea when stating I'd "have to manually list out the possible constructors and generate overloads for create". I just disliked the manually, so I did it by template meta programming. Thanks for the hint!

    #include <memory>
    #include <iostream>
    #include <tuple>
    
    
    //"perfect factory method"
    //usage: std::unique_ptr<Type> pt(create<Type>(Type-constructor-arguments));
    template <typename Type, typename ... ConstructorArgs>
    auto create(ConstructorArgs&& ... args){
        std::cerr << "calling " << __PRETTY_FUNCTION__ << std::endl;
        return std::make_unique<Type>(std::forward<ConstructorArgs>(args)...);
    }
    
    //Abstract Factory Variadic class. This is also generic case that ends recursion
    template <typename BaseType, typename TupleListOfConstructorArgs>
    class IFactory
    {
        static_assert(std::is_same<TupleListOfConstructorArgs, std::tuple<>>::value, "");
        public:
            //this method shall never be instatiated, it just exists to satisfy the "using BaseFactory::create" in IFactory
            template <typename T> void create(){ static_assert(sizeof(BaseType) + sizeof(T) < 0, ""); }
            virtual ~IFactory() = default;
    };
    
    //Abstract Factory Variadic class specialization to perform inheritance recursion
    template <typename BaseType, typename ... CurrentTupleArgs, typename ... TupleListOfConstructorArgs>
    class IFactory<BaseType, std::tuple<std::tuple<CurrentTupleArgs...>, TupleListOfConstructorArgs...>> : public IFactory<BaseType, std::tuple<TupleListOfConstructorArgs...>>
    {
        public:
            using BaseFactory = IFactory<BaseType, std::tuple<TupleListOfConstructorArgs...>>;
            using BaseFactory::create;
            virtual std::unique_ptr<BaseType> create(const CurrentTupleArgs&  ... args) const = 0;
    };
    
    //Concrete Factory Variadic class. This is also generic case that ends inheritance recursion
    template <typename BaseType, typename DerivedType, typename TupleListOfConstructorArgs, typename FullTupleListOfConstructorArgs>
    class CFactory : public IFactory<BaseType, FullTupleListOfConstructorArgs>
    {
        static_assert(std::is_same<TupleListOfConstructorArgs, std::tuple<>>::value, "");
        public:
            using Base = IFactory<BaseType, FullTupleListOfConstructorArgs>;
    };
    
    //Concrete Factory Variadic class specialization to perform inheritance recursion
    template <typename BaseType, typename DerivedType, typename ... CurrentTupleArgs, typename ... TupleListOfConstructorArgs, typename  FullTupleListOfConstructorArgs>
    class CFactory<BaseType, DerivedType, std::tuple<std::tuple<CurrentTupleArgs...>, TupleListOfConstructorArgs...>, FullTupleListOfConstructorArgs> : public CFactory<BaseType, DerivedType, std::tuple<TupleListOfConstructorArgs...>, FullTupleListOfConstructorArgs>
    {
        public:
            using BaseFactory = CFactory<BaseType, DerivedType, std::tuple<TupleListOfConstructorArgs...>, FullTupleListOfConstructorArgs>;
            using BaseFactory::create;
    
            std::unique_ptr<BaseType> create(const CurrentTupleArgs&  ... args) const override
            {
                std::cerr << "calling " << __PRETTY_FUNCTION__ << std::endl; 
                return ::create<DerivedType>(args...);
            }
    };
    
    template <typename BaseType, typename DerivedType, typename TupleListOfConstructorArgs>
    using CFactoryFrontend = CFactory<BaseType, DerivedType, TupleListOfConstructorArgs, TupleListOfConstructorArgs>;
    
    class TimerInterface {
        public:
            TimerInterface() = default;
            virtual ~TimerInterface() = default;
            TimerInterface (const char* name) {}
            TimerInterface(int& x, const char* name) { std::cerr << "calling " << __PRETTY_FUNCTION__ << std::endl;  }
            TimerInterface(const int& x, const char* name) { std::cerr << "calling " << __PRETTY_FUNCTION__ << std::endl;  }
            virtual void whoami() const = 0;
            /*...*/
    };
    
    class Timer: public TimerInterface {
        public:
            Timer() = default;
            Timer(const char* name) : TimerInterface (name) {}
            Timer(int& x, const char* name) : TimerInterface(x, name) {}
            Timer(const int& x, const char* name) : TimerInterface(x, name) {}
            void whoami() const override { std::cerr << "I'm real!" << std::endl; }
            /*...*/
    };
    
    
    class TimerMock : public TimerInterface {
        public:
            TimerMock () = default;
            TimerMock (const char* name) : TimerInterface (name) {}
            TimerMock (int& x, const char* name) : TimerInterface(x, name) {}
            TimerMock (const int& x, const char* name) : TimerInterface(x, name) {}
            void whoami() const override { std::cerr << "I fake it!" << std::endl; }
            /*...*/
    };
    
    //using TimerInterfaceConstructors = std::tuple<std::tuple<>, std::tuple<const char*>>;
    using Constructors = std::tuple<std::tuple<>, std::tuple<const char*>, std::tuple<int&, const char*>, std::tuple<const int&, const char*>>;
    
    using TestFactory = CFactoryFrontend<TimerInterface, Timer, Constructors>;
    
    using TimerFactory = CFactoryFrontend<TimerInterface, Timer, Constructors>;
    using TimerMockFactory = CFactoryFrontend<TimerInterface, TimerMock, Constructors>;
    
    using TimerFactoryInterface = TimerFactory::Base;
    
    
    class ClassUnderTest {
        public:
            std::unique_ptr<TimerInterface> timer {};
            std::unique_ptr<TimerInterface> timer2 {};
        
            ClassUnderTest(const TimerFactoryInterface& factory)
            : timer(factory.create("I got a name!"))
            , timer2(factory.create())
            {}
    };
    
    class Production
    {
        public:
            ClassUnderTest realUsage;
            
            Production() :
            realUsage(TimerFactory())
            {}
    };
    
    class Test
    {
        public:
            ClassUnderTest tested;
            
            Test() :
            tested(TimerMockFactory())
            {}  
    };
    
    int main()
    {
        Production p;
        p.realUsage.timer->whoami();
        
        Test t;
        t.tested.timer->whoami();
    
        TestFactory tf;
        TimerFactoryInterface& tfi(tf);
    
        const char* x = "X";
        tfi.create();
        tfi.create(x);
    
        int y;
        const int cy = 17;
        tfi.create(y, x);
        tfi.create(cy, "name");
    
        ::create<Timer>(x);
    }
    
    

    It compiles using GCC-6 and newer and clang with -std=c++-14