Search code examples
c++design-patternsfactory-pattern

Factory design pattern optimization


I have a class named Creation in both Creation.cc and Creation.h, and there are bunch of createA, createB, createC...etc. functions inside this class, all of them will return a pointer type.

I have a lot of modelA.cc, modelB.cc, modelC.cc ...etc. files, and all of them have included Creation.h.

Since whenever I make a new model x (make a new modelx.cc), I need to add the corresponding createx in Creation.h, which will make all model.cc files being compiled again.

All the createA, createB, createC functions have same parameter list but different input values and implementation, which are based on their model.cc.

My goal is that I don't want to recompile all model.cc when adding a new createx function.

Thanks.


Solution

  • A common strategy is to have the factory contain a registration method. Once a class is registered then a call is made to the factory to get an actual instance.

    The C++17 example below allows sub-classes of the 'interface' to have different parameters in the call for creation. 'createInstance' is tasked with constructing an instance for a particular class.

    The Any class was taken from here. As noted in the link, the createInstance call is quite particular about the input arguments matching the method signature.

    #include <iostream>
    #include <functional>
    #include <map>
    #include <string>
    #include <any>
    #include <functional>
    #include <map>
    #include <string>
    #include <iostream>
    
    struct interface
    {
    };
    
    
    template<typename Ret>
    struct AnyCallable
    {
        AnyCallable() {}
        template<typename F>
        AnyCallable(F&& fun) : AnyCallable(std::function(fun)) {}
        template<typename ... Args>
        AnyCallable(std::function<Ret(Args...)> fun) : m_any(fun) {}
        template<typename ... Args>
        Ret operator()(Args&& ... args) 
        { 
            return std::invoke(std::any_cast<std::function<Ret(Args...)>>(m_any), std::forward<Args>(args)...); 
        }
        std::any m_any;
    };
    
    struct factory
    {
       std::map<std::string,  AnyCallable<interface>> registry;
       void registerClass(std::string const & class_name, AnyCallable<interface> function)
       {
          registry[class_name] = function;
       }
    
       template<typename ... Args>
       interface createInstance(std::string const & class_name, Args && ... args)
       {
          if(registry.find(class_name) == registry.end())
          {
             throw "No class found";
          }
          return registry[class_name](std::forward<Args>(args)...); 
       }
    };
    
    
    struct A:public interface
    {
       A() 
       {
          std::cout << "Create A" << std::endl;
       }
    
       static interface createInstance(int t)
       {
          return A();
       } 
    
       static void registerWithFactory(factory& f)
       {
          f.registerClass("A",&createInstance);
       }
    };
    
    
    struct B:public interface
    {
       B() 
       {
          std::cout << "Create B" << std::endl;
       }
    
       static interface createInstance(std::tuple<int, double> t)
       {
          return B();
       } 
    
       static void registerWithFactory(factory& f)
       {
          f.registerClass("B",&createInstance);
       }
    };
    
    int main(int argc, char* argv[])
    {
        factory f;
    
        A::registerWithFactory(f);
        B::registerWithFactory(f);
        try {
            interface a = f.createInstance("A",1);
            interface b = f.createInstance("B",std::tuple{1,1.0});
            interface c = f.createInstance("C");
        }
        catch(...)
        {
           std::cout << "createInstance failed" << std::endl;
        }
        return 0;
    }
    

    All the members of the factory will descend from 'interface'. The 'factory' will allow registration of new class that are not yet created. In the example A and B exists but C does not. In the future C can be added without recompiling the existing code.

    There are a variety of patterns that expand on this theme.