根据用户输入创建相应派生类的最佳做法是什么?

时间:2016-02-16 14:25:57

标签: c++ design-patterns

目前,我有一个接口和几个具体的实现类,我得到了string类型的用户输入。 我想根据用户输入new对应的类。做这些事情的最佳做法是什么?

似乎"工厂模式"可以用于此。现在,我使用enumunordered_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;
}

还有什么,在我目前的情况下,所有派生类都有相同的构造函数参数,如果不同的类有不同的构造函数参数怎么办?

2 个答案:

答案 0 :(得分:2)

目前尚不清楚enum如何帮助你:

  1. 这似乎是重复,因为它基本上是在进行工厂的翻译(所以你有一个用户输入到enum的翻译,以及一个枚举到类的翻译)

  2. 它限制你只使用可以枚举的东西(例如,你现在不能传递给ctor“除用户输入的最后3位数之外的所有东西”,因为那不是可枚举的东西)

  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写为非匿名类型。一些额外的样板,但没有什么棘手的。