Home > Software engineering >  Design pattern: Templated classes inside a generic class used by multiple components
Design pattern: Templated classes inside a generic class used by multiple components

Time:03-06

Let us say I got a Factory method which provides some method to create socket connections. This class is templated using a CRTP pattern where the actual implementation of the factory method is fed into the generic FactoryConn class:

 template<typename T>
 class FactoryConn
 {
     auto config() { return static_cast<T&>(*this)->config() }
  }

  class FactoryProduction : FactoryConn<FactoryProduction>
  {
      auto config() { ... implementation ...}; // auto explanation below. 
   }

The return type of config is determined by the specific Factory implementation. The factory method wires a few components and returns an injector configuration. The reason is that for testing we would like to pass another Factory implementation mocking the actual methods. This is best achieved using a CRTP pattern since the return type is deduced using the template deductions.

Now we wan to have a generic class which holds the various factory methods and provides an interface get() methods to return the various (in this case the actual FactoryConn). The idea is we want APP to be used by various components and they should not know what the type really is.A component should work for any of the actual factory methods

   class APP { 

     getFactory ( ) // return ActualFactory 

     ActualFactory factoryConn
   }

Now every component in my code base would have some sort of a shared pointer to the single instance of ´class APP´ and use it to get hold of the various factory classes. I could template class APP but that is not the best solution because the components would need to specify the template argument, which is exactly what I want to avoid. I would rather have that the main function does this and the component get injected the class APP.

I have read about patterns regarding type erasure and std::variant which might be helpful in my case. Any ideas?

CodePudding user response:

You either need a variant of all the types that could be returned by config() (which may or may not be possible in your case) or you need to return some kind of (smart) pointer to an interface type that lets you erase the actual type of the config. Alternatively you could just make the type returned by config() always the same.

If you specifically want to use CRTP for only testing, theres another pattern I have used which is to have a variant inside of FactoryConn and then use visit inside of config(), so instead of CRTP we are using composition and bounded polymorphism. You can of course then put the types used for testing behind a preprocessor check so in production you have a variant of size 1 and during testing you have a variant that also holds your test type (the compiler should optimise away that 1 sized variant).

So, something like this:

class FactoryProduction
  {
      auto config() { ... implementation ...}; // auto explanation below. 
   };
using factory_store = std::variant<FactoryProduction
#if SOME_TESTING_CHECK
,FactoryMock
#endif
>;
 class FactoryConn
 {
     factory_store store;
     template<typename F>
     auto config(F&& visitor) -> void { 
         std::visit([visitor = std::forward<F>(visitor)](auto&& s) mutable {
             std::invoke(std::forward<F>(visitor), s.config());
         }, store);
  };

Note that we have to take a callback for the config, because std::visit only has a well-defined return type if the result is the same for all its stored types.

  • Related