【问题标题】:Problems with the universal factory method and the variadic templates通用工厂方法和可变参数模板的问题
【发布时间】:2014-02-19 09:49:02
【问题描述】:

我想创建类似通用工厂方法的东西 - 看看这个:

template <class BaseType>
class Factory {
  public:
    template <class ... Args>
    static BaseType* Create(const Args& ... args) {
      return new DerivedType(args ...);
    }
};

其中DerivedType 是从BaseType 派生并在不同位置定义的其他类型。

问题在于存储DerivedType。我想这样做,例如,像这样:

void f() {
  // Derived type may have more than one constructor,
  // that's why I suggest using of the variadic templates.
  BaseType* ptr1 = Factory<BaseType>::Create("abc", 5, 10.);
  BaseType* ptr2 = Factory<BaseType>::Create();
  ...
}

...

Factory<BaseType>::SetType<MyDerivedType>();
f();

Factory<BaseType>::SetType<YourDerivedType>();
f();

我可以设置不同的派生类型,但它们在编译时都是已知的。 我想不出合适的技术来做到这一点。

问题:你能推荐一个吗?


这样做的基本原理(因此,原始问题,如果有人认为问题本身就是 XY 问题) - 是一种对代码的一些棘手部分进行单元测试的能力。例如,如果我有一个代码:

...
Shuttle* shuttle1 = new ShuttleImpl("Discovery", Destination::Moon);
Shuttle* shuttle2 = new ShuttleImpl();
...

而且我不想在每次运行单元测试时都真正构建穿梭:

class Shuttle: public Factory<Shuttle> { ... }
...
Shuttle* shuttle1 = Shuttle::Create("Discovery", Destination::Moon);
Shuttle* shuttle2 = Shuttle::Create();
...

所以,在单元测试中我可以这样做:Shuttle::SetType&lt;TestShuttle&gt;();

可能有更多“可测试”的类,这就是为什么我需要为所有这些类建立一个通用工厂:

class Car: public Factory<Car> { ... }
class Driver: public Factory<Driver> { ... }
...

【问题讨论】:

  • 我猜测某种形式的类型擦除和模仿std 分配器的接口将是探索的方向,但不知道它是否真的可能。
  • 为什么不能使用第二个模板参数,例如:template &lt;class Base, class Derived&gt; class Factory;
  • @CouchDeveloper - 因为代码中可以有多个Derived:即我可以使用完整的模拟测试相同的代码,或者使用特殊的测试类来检查方法的数量调用。
  • 您不能拥有需要不同实现的 same 代码。但是,template 允许您拥有实例化为不同代码的 same 模板。正是您要查找的内容,除非我不明白您的问题。
  • @CouchDeveloper - 然后将您的解决方案作为示例代码提出,如果您误解我的问题,我会尽力向您展示。

标签: c++ unit-testing c++11 factory-pattern variadic-templates


【解决方案1】:

不是一个完整的答案,但你的类模板的 Create 静态函数模板应该是:

template <class BaseType>
class Factory {
  public:
    template <class... Args>
    static BaseType* Create(Args&&... args) {
      return new DerivedType(std::forward<Args>(args)...);
    }
};

另见When to use std::forward to forward arguments?


编辑:

为什么第二个模板参数不能解决您的问题?

例如:

template <class Base, class Derived>
class Factory {
  public:
    template <class... Args>
    static Base* Create(Args&&... args) {
      return new Derived(std::forward<Args>(args)...);
    }
};

代替

Factory<BaseType>::SetType<MyDerivedType>();
f();

Factory<BaseType>::SetType<YourDerivedType>();
f();

你可以写:

Factory<MyBase, MyDerived1> factory1;
Factory<MyBase, MyDerived2> factory2;

auto object1 = factory1::Create(1, "a");
auto object2 = factory2::Create(1.2, "abc");

【讨论】:

  • 第二个模板参数并不能解决问题,因为我必须不改变对Create()的原始调用。 IE。我的示例中有由函数f() 表示的代码,它是不可变的,并且我有单元测试,它必须更改f() 中的一些对象。否则,如果我必须将原始代码更改为具有不同Deriveds 的每个调用,例如f1()f2() 等,那将毫无意义。我的示例代码很简单,而实际程序要复杂得多。
  • 另外,就我的示例代码而言,您的答案中的object1object2 应该同时使用相同的派生类型。
  • @abyss.7 您应该意识到,如果您不能更改从模板创建的Create 的代码 - 就无法注入模拟。你必须在运行时做这些事情。然而,这需要完全不同的设计。
  • 我不抱怨在运行时这样做,但它如何暗示我的设计是错误的?此外,我已经使用这种设计解决了这个问题,只有一个默认构造函数。如果您能提出更好的设计建议,我将不胜感激。
  • @abyss.7 您不能像在您的示例(这是一个模板)中那样“切换”类型 DerivedType 以获得不同的类型。您也不能在运行时设置“类型”。您可以在运行时设置“对象”。这可能意味着一个函数,它创建你的对象。但是,您的 Factory 方法没有多大意义,因为它会将对象创建完全委托 到定义为“外部”的函数。此外,不能通过可变参数模板包设置参数。您可能会使用元组。这一切都意味着,您最初的设计甚至无法远程应对。你需要完全不同的东西。
【解决方案2】:

如果您的工厂知道所有可能的派生类,以下可能会有所帮助:

// get_index<T, T1, .., TK-1, T, Ts...> is std::integral_constant<std::size_t, K>
template <typename T, typename ... Ts> struct get_index;

template <typename T, typename ... Ts>
struct get_index<T, T, Ts...> : std::integral_constant<std::size_t, 0> {};

template <typename T, typename Tail,  typename ... Ts>
struct get_index<T, Tail, Ts...> :
        std::integral_constant < std::size_t, 1 + get_index<T, Ts...>::value > {};

template <typename Base, typename...Deriveds>
struct Factory
{
private:
    template <typename Derivated, typename...Ts>
    static constexpr Base* allocator(Ts&&...args)
    {
        return new Derivated(std::forward<Ts>(args)...);
    }

    template <typename...Ts>
    static constexpr std::array<Base*(*)(Ts&&...), sizeof...(Deriveds)>
    array_alloc()
    {
        return std::array<Base*(*)(Ts&&...), sizeof...(Deriveds)>
            {{ &allocator<Deriveds, Ts&&...>... }};
    }

public:
    template <typename...Ts>
    static Base* create(Ts&&...args)
    {
        return array_alloc<Ts...>()[active](std::forward<Ts>(args)...);
    }

    template <typename Derived>
    static void setType()
    {
        active = get_index<Derived, Deriveds...>::value;
    }

private:
    static std::size_t active;
};

template <typename Base, typename...Deriveds>
std::size_t Factory<Base, Deriveds...>::active = 0;

并像这样使用它:

class Base {};

struct D1 : Base {
    D1() {std::cout << "D1" << std::endl;}
    D1(int a, int b) {}
};

struct D2 : Base {
    D2() {}
    D2(int a, int b) { std::cout << "D2(" << a << ", " << b << ")" << std::endl; }
};

int main(int argc, char *argv[])
{
    typedef Factory<Base, D1, D2> BaseFactory; // default to D1

    Base* b1 = BaseFactory::create(); // D1()
    BaseFactory::setType<D2>();
    Base* b2 = BaseFactory::create(42, 53); // D2(42, 53)

    delete b2;
    delete b1;

    return 0;
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-11-05
    • 2018-11-26
    • 2011-09-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多