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:
- 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 theCreator()
method. Is there a way to fix this? This is the reason why creation of obj3 fails. -
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