Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Factory with switch over enum that instantiates templates

I have the following hierarchy pattern in various places in the codebase:

enum DerivedType {
    A, B, C };

class Base {
public:
  static Base* Create(DerivedType t);
};

template <DerivedType T>
class Derived : public Base {
};

The Create method returns a new object of class Derived<A>, Derived<B>, or Derived<C>, depending on its argument:

Base* Base::Create(DerivedType t) {
  switch (t) {
  case A: return new Derived<A>;
  case B: return new Derived<B>;
  case C: return new Derived<C>;
  default: return NULL;
  }
}

The problem is that there are many such Base -> Derived hierarchies, with essentially the same implementation of Create() copy-pasted all over the place. Is there an elegant, yet easy-to-understand way to avoid duplication here?

like image 952
krlmlr Avatar asked Jun 01 '16 19:06

krlmlr


3 Answers

We can abstract away the Factory details, and rely on the client to provide us the mapping of enum to class type:

template<typename ENUM, typename T>
struct Factory
{
    typedef std::map<ENUM, T*(*)()> map_type;
    static map_type factoryMapping_;

    static T* Create(ENUM c)
    {
        return factoryMapping_[c]();
    }

    static void Init(map_type _mapping)
    {
        factoryMapping_ = _mapping;
    }
};

template<typename ENUM, typename T>
typename Factory<ENUM, T>::map_type Factory<ENUM,T>::factoryMapping_;

Now it's the client's job to provide us methods for creating a Base* given some enum value.

If you're willing and able to abstract away the creation of a derived class with a template, then you can save a fair bit of typing.

What I mean is, let's create a templated function to create a derived class (without any real checking for correctness):

template<typename Base, typename Derived>
Base* CreateDerived()
{
    return new Derived();
}

Now I can define an enum and associated class hierarchy:

enum ClassType {A, B};
struct Foo
{
    virtual void PrintName() const
    {
        std::cout << "Foo\n";
    }
};

typedef Factory<ClassType, Foo> FooFactory ;

struct DerivedOne : public Foo
{
    virtual void PrintName() const
    {
        std::cout << "DerivedOne\n";
    }
};

struct DerivedTwo : public Foo
{
    virtual void PrintName() const
    {
        std::cout << "DerivedTwo\n";
    }
};

And then use it like so:

// set up factory
std::map<ClassType, Foo*(*)()> mapping;
mapping[A] = &CreateDerived<Foo, DerivedOne>;
mapping[B] = &CreateDerived<Foo, DerivedTwo>;
FooFactory::Init(mapping);

// Use the factory
Foo* f = FooFactory::Create(A);
f->PrintName();

Live Demo

Of course this simplifies your problem a bit, namely moving the factory details out of the base class and ignoring for a minute that the children themselves are templated. Depending on how hard it is in your domain to create a good CreateDerived function for each type, you may not end up saving a ton of typing.

EDIT: We can modify our Create function to return NULL by taking advantage of std::map::find. I omitted it for brevity. If you concerned about performance, then yes, an O(log n) search is slower asymptotically than a simple switch, but I strongly doubt this will wind up being the hot path.

like image 137
AndyG Avatar answered Nov 10 '22 06:11

AndyG


You could make it a template

template<typename Base, typename Derive, template<Derive> class Derived>
Base* Base::Create(Derive t) {
  switch (t) {
  case Derive::A: return new Derived<Derive::A>;
  case Derive::B: return new Derived<Derive::B>;
  case Derive::C: return new Derived<Derive::C>;
  default: return nullptr;
  }
}

but that assumes that there are only ever A, B, and C in struct enum Derive.

like image 24
Walter Avatar answered Nov 10 '22 04:11

Walter


Instead of using a C++ style enum you could use a Java style enum where each value is a singleton of a class derived from a common base.

Then define a set of pure virtual functions on the base class that create the desired flavour of derived class, and implement them appropriately in each of the singletons.

Then instead of switch (t) ... you can use t->createDerived().

Or to put it more succinctly: replace switch with polymorphism.

like image 1
Alan Stokes Avatar answered Nov 10 '22 05:11

Alan Stokes