jeudi 3 mai 2018

Implementation of a generic factory design pattern

I like using factory design pattern to inject dependencies, but that means having lots of very similar classes (almost one factory per class). Then I got to an idea to create a generic factory pattern using templates. Something like this:

// g++ -std=c++14 -Wall -Wextra factory.cpp -o factory


#include <functional>
#include <memory>
#include <utility>
#include <iostream>



template < typename T, typename Base = typename T::Iface >
class Factory
{
public:

    template < typename... Args >
    using CreatorFn = std::function< std::shared_ptr< Base > ( Args&&... ) >;

    template < typename... Args >
    static std::shared_ptr< Base > Create( Args&&... args );

    template < typename... Args >
    static void ResetToDefaultCreator();

    template < typename... Args >
    static void SetCreator( CreatorFn< Args... > fn );

private:
    Factory() = delete;

    template < typename... Args >
    static CreatorFn< Args... >& Creator();

    template < typename... Args >
    static std::shared_ptr< Base > DefaultCreator( Args&&... args );
};



template < typename T, typename Base >
template < typename... Args >
std::shared_ptr< Base > Factory< T, Base >::Create( Args&&... args )
{
    return Creator< Args... >()( std::forward< Args >( args )... );
}


template < typename T, typename Base >
template < typename... Args >
void Factory< T, Base >::ResetToDefaultCreator()
{
    CreatorFn< Args... >() = DefaultCreator< Args... >;
}

template < typename T, typename Base >
template < typename... Args >
void Factory< T, Base >::SetCreator( CreatorFn< Args... > fn )
{
    Creator< Args... >() = fn;
}

template < typename T, typename Base >
template < typename... Args >
Factory< T, Base >::CreatorFn< Args... >& Factory< T, Base >::Creator()
{
    static CreatorFn< Args... > creator = DefaultCreator< Args... >;
    return creator;
}

template < typename T, typename Base >
template < typename... Args >
std::shared_ptr< Base > Factory< T, Base >::DefaultCreator( Args&&... args )
{
    return std::make_shared< T >( std::forward< Args >( args )... );
}


struct A {
    virtual ~A() = default;

    virtual void foo() = 0;
};

struct B : public A {
    using Iface = A;

    virtual void foo()
    {
    }
};

struct C : public A {
    using Iface = A;

    C( int, float )
    {
    }

    virtual void foo()
    {
    }
};

struct D : public A {
    using Iface = A;

    D( int, float )
    {
    }

    virtual void foo()
    {
    }
};

using FactoryDefaultConstructor = Factory< B >;
using FactoryParamsConstructor  = Factory< C >;



int main()
{
    FactoryParamsConstructor::ResetToDefaultCreator<int,float>();

    std::shared_ptr< A > obj1 = FactoryParamsConstructor::Create( 3, 5 );
    C* realObj1 = dynamic_cast< C* >( obj1.get() );
    if ( nullptr != realObj1 )
    {
        std::cout << "1 created" << std::endl;
    }
    else
    {
        std::cout << "1 failed" << std::endl;
    }

    FactoryParamsConstructor::CreatorFn< int, float > newCretorFn = []( int a,float b ){
                                    std::cout << "****cb called"<<std::endl;
                                    return std::shared_ptr< A >( new D( a, b ) );
                            };
    FactoryParamsConstructor::SetCreator< int, float >( newCretorFn );
    std::shared_ptr< A > obj2 = FactoryParamsConstructor::Create( 3, 5.7f );
    D* realObj2 = dynamic_cast< D* >( obj2.get() );
    if ( nullptr != realObj2 )
    {
        std::cout << "2 created" << std::endl;
    }
    else
    {
        std::cout << "2 failed" << std::endl;
    }

    float p = 5.5f;
    std::shared_ptr< A > obj3 = FactoryParamsConstructor::Create( 3, p );
    D* realObj3 = dynamic_cast< D* >( obj3.get() );
    if ( nullptr != realObj3 )
    {
        std::cout << "3 created" << std::endl;
    }
    else
    {
        std::cout << "3 failed" << std::endl;
    }
}

Output:

1 created
****cb called
2 created
3 failed

This works, but with certain problems:

  1. if I don't take care what I pass to the Create() method, it can fail, since it is going to use wrong instance of the Creator() method. Is there a way to fix this? This is the reason why creation of obj3 fails.
  2. The SetCreator() method can take only std::function objects. I understand why. My question is, can I change it to take anything appropriate and call correct Creator() method? Ideally, it would have this declaration:

    template < typename F > static void SetCreator( F fn );

Then I could do this:

std::shared_ptr< A > foo( int, float ) { return new B; };
FactoryParamsConstructor::SetCreator( foo );

Aucun commentaire:

Enregistrer un commentaire