目前,我有一个接口和几个具体的实现类,我得到了string
类型的用户输入。
我想根据用户输入new
对应的类。做这些事情的最佳做法是什么?
似乎"工厂模式"可以用于此。现在,我使用enum
,unordered_function
和特定函数来处理它,代码如下:
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;
}
还有什么,在我目前的情况下,所有派生类都有相同的构造函数参数,如果不同的类有不同的构造函数参数怎么办?
答案 0 :(得分:2)
目前尚不清楚enum
如何帮助你:
这似乎是重复,因为它基本上是在进行工厂的翻译(所以你有一个用户输入到enum的翻译,以及一个枚举到类的翻译)
它限制你只使用可以枚举的东西(例如,你现在不能传递给ctor“除用户输入的最后3位数之外的所有东西”,因为那不是可枚举的东西)
相反,如何使工厂依赖于用户输入?你可以做很多功能的事情:
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.
答案 1 :(得分:1)
你想走多远?
我们可以从策略创建编译时映射,将运行时映射到编译时间值的魔术开关,在各种元工厂之间智能选择的分层输入处理系统等。
我吗?如果你的枚举和你的策略紧密结合,那就让它更紧凑:
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 {};
然后,如果您有一个魔术开关,可以自动为您编写get_strategy
。
IStrategy* pStrat = magic_switch( eStrat, [&](auto strat)->IStrategy* {
constexpr StrategyEnum e = static_cast<StrategyEnum>(static_cast<std::size_t>(strat));
return new Strategy<e>{};
});
与您编写的开关/案例相比,代码重复次数更少,尤其是在类型数量开始变大的情况下。但是magic_switch
的样板很大,很复杂而且很棘手。
现在,如果您的策略需要不同的数据,您需要一种方法来提供它们。最简单的方法是生成某种数据源结构,从中可以获得所需的数据。
然后要么他们有一个获取数据源的构造函数,要么你编写一个获取数据源的函数,获取它需要的数据,然后构造对象。
然而,#1规则是“你可能不需要它”。弄清楚你需要什么。隐藏详细信息。实施和运输。所有这些都是严重的过度工程。
这是一个神奇的开关:
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) );
}
};
接受一个count,一个索引和一个lambda,然后使用索引的编译时值调用lambda(假设它小于count)并返回结果。
使用此方法,您可以将用户选择的枚举的运行时值转换为编译时已知的枚举值。然后,您可以将编译时已知的枚举值映射到正确的构造函数代码。
实际上,这可以让您合成get_strategy
样板。
它是过度工程,但是一种选择。
请注意,一些其他方面很好的C ++编译器在上述法律代码中失败了。在它们上,您需要手动将invoke
中使用的lambda写为非匿名类型。一些额外的样板,但没有什么棘手的。