Reputation: 3387
Currently, I have an interface and several concrete implementation class, and I got string
type user input.
I want to new
corresponding class according to user input. What is the best practice to do these stuff?
It seems that the "factory patten" can be used for this. Now, I use an enum
, an unordered_function
and a specific function to handle it, the code is like below:
class IStrategy{};
class A : public IStrategy{};
class B : public IStrategy{};
class C : public IStrategy{};
class D : public IStrategy{};
enum StrategyEnum
{
A = 0,
B,
C,
D,
};
const std::unordered_map<std::string, StrategyEnum> ms_lut{
{"A", A},
{"B", B},
{"C", C},
{"D", D}};
Strategy* get_strategy(StrategyEnum s)
{
Strategy* s;
switch(s)
{
case A:
s = new A;
break;
case B:
s = new B;
break;
case C:
s = new C;
break;
case D:
s = new C;
break;
default:
cerr << "Unsupported strategy!" << endl;
}
return s;
}
What's more, in my current situation, all derived class have the same constructor parameter, what if different class has different constructor parameter?
Upvotes: 2
Views: 122
Reputation: 275385
How far do you want to go?
We could create a compile-time mapping from strategies, a magic switch that maps run time to compile time values, a layered input processing system that intelligently picks between various meta-factories, etc.
Me? If your enum and your strategies are tightly coupled, make it tighter:
class IStrategy{};
enum class StrategyEnum {
A = 0,
B,
C,
D,
Strategy_Count, // must be LAST in enum
};
template<StrategyEnum> class Strategy;
template<> class Strategy<A>: public IStrategy {};
template<> class Strategy<B>: public IStrategy {};
template<> class Strategy<C>: public IStrategy {};
template<> class Strategy<D>: public IStrategy {};
Then if you have a magic switch hanging around, get_strategy
can be auto-written for you.
IStrategy* pStrat = magic_switch( eStrat, [&](auto strat)->IStrategy* {
constexpr StrategyEnum e = static_cast<StrategyEnum>(static_cast<std::size_t>(strat));
return new Strategy<e>{};
});
which has less code duplication than the switch/case you wrote, especially if the number of types starts to get bigger. But the boilerplate of magic_switch
is large and complex and tricky.
Now, if your strategies need different data, you need a way to provide it to them. The easiest way would be to produce a data-source structure of some kind from which each can get the data they need.
Then either they have a constructor that takes a data source, or you write a function that takes the data-source, gets the data it needs, then constructs the object.
However, the #1 rule is "you probably won't need it". Figure out what you need. Hide the details. Implement and ship. All of the above is serious over-engineering.
Here is a magic switch:
template<std::size_t count>
struct magic_switch_t {
private:
template<std::size_t I>
using Index = std::integral_constant<std::size_t, I>;
template<class F>
using R=std::result_of_t<F&(Index<0>)>;
template<class F, std::size_t...Is>
R<F> invoke( std::index_sequence<Is...>, std::size_t i, F&& f ) const {
using pf = R<F>(*)(F&);
pf table[] = {
+[](F& f)->R<F>{ return std::forward<F>(f)(Index<Is>{}); }...
};
return table[i](f);
};
public:
template<class F>
R<F> operator()( std::size_t i, F&& f) const {
return invoke( std::make_index_sequence<count>{}, i, std::forward<F>(f) );
}
};
which takes a count and an index and a lambda, then invokes the lambda with the compile-time value of the index (assuming it is less than count) and returns the result.
Using this you can convert the run-time value of what enum the user picked, to a compile-time known enum value. Then you can map that compile-time known enum value to the proper constructor code.
In effect, this lets you synthesize the get_strategy
boilerplate.
It is overengineering, but an option.
Note that some otherwise fine C++ compilers fail on the above legal code. On them, you need to write the lambda used in invoke
manually as a non-anonymous type. A bit of extra boilerplate, but nothing tricky.
Upvotes: 1
Reputation: 76297
It's not clear how the enum
is helping you here:
It seems like duplication, as it's basically doing a translation that the factory could (so you have one translation of user input to enum, and one translation of enum to class)
It constricts you to use use only things that can be enumerated (e.g., you can't pass now to the ctor "everything except the last 3 digits from the user input", because that's not something that's enumerable).
Instead, how about making the factory depend on the user input? You could do very versatile stuff:
Strategy* get_strategy(const string &s)
{
// If the input is "foo", return A{}
if(s == "foo")
return new A{};
// If the input starts with "bar", return B{s}
if(s.substr(0, 3) == "bar")
return new B{s}
// etc.
Upvotes: 2