How to implement a factory with multiple switches?

2019-02-21 21:48发布

问题:

I want to implement a factory function for creating objects. My object template looks like this:

template <typename TA, typename TB>
struct MyImpl : public MyInterface
{
    // content
};

and my factory is like this:

MyInterface* factory(char ta, char tb)
{
    if(ta == 'c' && tb == 'c')
    {
        return new MyImpl<char, char>();
    }
    if(ta == 'c' && tb == 's')
    {
        return new MyImpl<char, short>();
    }
    if(ta == 's' && tb == 'c')
    {
        return new MyImpl<short, char>();
    }
    // and so on ....
}

The factory function must accept non-static char data (ta, tb) for it could not be determined at compile time, and I think that's the whole point of this factory. In fact, ta and tb are read from a file (or network).

I want a simpler solution to avoid the annoying 2-level switch.

I think my question is similar to how-would-one-write-a-meta-if-else-if-in-c with the exception that I cannot use static parameters.

Perhaps I should just fallback to C macros and use some macro tricks to shrink my current code?

Thanks in advance!

UPDATE

Answer to @Rob:

My actual code would be more complex with many other stuffs in it and harder to read and not related in many aspects. I'm trying to get the pseudo-code right, If there is any problem, please kindly inform me :-).

Answer to @Dynguss:

My problem is that in my actual implementation, parameters of the factory (ta, tb) would be large in range, like 10 X ta and 20 X tb, and combination of ta and tb would be very long in lines, and hard to maintain. So I need at least some way to ease the combination efforts.

回答1:

Here's an idea:

template <typename T>
MyInterface * factroy(char t)
{
    if (t == 'c') { return MyImpl<T, char>();  }
    if (t == 's') { return MyImpl<T, short>(); }
    // ...
}

MyInterface * factory(char ta, char tb)
{
    if (ta == 'c') { return factroy<char>(tb);  }
    if (ta == 's') { return factroy<short>(tb); }
    // ...
}

With variadic templates, this pattern could be extended to any number of type arguments – for example:

struct Base { virtual ~Base() = default; };
template <typename A, typename B, typename C> struct Foo : Base { };

#include <tuple>

template <typename ...Args>
constexpr Base * factory(std::tuple<Args...>)
{
    return new Foo<Args...>;
}

template <typename ...Args, typename ...Char>
constexpr Base * factory(std::tuple<Args...>, char t, Char ... ts)
{
    return t == 'c' ? make(std::tuple<char,      Args...>(), ts...)
         : t == 's' ? make(std::tuple<short int, Args...>(), ts...)
         : t == 'i' ? make(std::tuple<int,       Args...>(), ts...)
         : t == 'l' ? make(std::tuple<long int,  Args...>(), ts...)
         : nullptr;
}

Usage: auto p = factory(std::tuple<>(), 'c', 's', 'l');



回答2:

If you could separate you Impl constructor into new Impl(new InnerImpl) then there is a possibility of a very involved solution that replaces switches with a map:

  struct Creator {
     virtual ~Creator(){};
     virtual Interface *create(InterfaceInner *) = 0;
     virtual InterfaceInner *createInner() = 0;
  }

  std::Map<char, Creator *> creatorMap;

  template<char T>
  struct Factory {
     Factory() {
       creatorMap.insert(T, &this->creator);
    }
  }


  template<>
  struct Factory<'s'> {
    struct ShortCreator : public Creator {
     virtual Interface *create(InterfaceInner *inner) {return new Impl<short>(inner);}
     virtual InterfaceInner *createInner(return new ImplInner<short>());
    } creator;
  }




  Factory<'s'> shortFactory;
  Factory<'c'> charFactory;

  creatorMap[ta].create(creatorMap[tb].createInner());


回答3:

What about:

MyInterface* factory(char ta, char tb)
{
    switch( ta << 8 | tb )
    {
    case 'cc': return MyImpl<char, char>();
    case 'cs': return MyImpl<char, short>();
    case 'sc': return MyImpl<short, char>();
    // and so on ....
    }
}

NOTE: This work fine on Intel/AMD x86 and x64, on CPU with different endianness (for example PPC), you must swap ta and tb like this: switch( ta | tb << 8 ).