2014-01-28 82 views
6

我想创造这样一个普遍的工厂方法 - 看看这一个:问题与通用工厂方法和可变参数模板

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

DerivedTypeBaseType衍生和定义在一些其他类型不同的地方。

问题是存储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<TestShuttle>();

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

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

我猜某种形式的类型擦除和模仿的'std'分配器的接口将是探索的方向,但不知道是否它实际上是可能的。 – Angew

+0

为什么你不能使用第二个模板参数,例如:'template class Factory;'? – CouchDeveloper

+0

@CouchDeveloper - 因为在代码中可以有多个“Derived”:即我可以用一个完整的模拟或者一个特殊的测试类来测试相同的代码,以检查方法调用的数量。 –

回答

0

不是一个完整的答案,但你的类模板的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"); 
+0

第二个模板参数不能解决问题,因为我*必须*不会将原始调用更改为'Create()'。即我的代码中有函数'f()'代表的代码,这是不可变的,我有单元测试,它必须改变'f()'中的一些对象。否则,如果必须将原始代码更改为具有不同'Derived'的每个调用,例如'f1()','f2()'等,我的示例代码很平凡,而真正的程序是更复杂。 –

+0

此外,在你的答案中的'object1'和'object2'应该一次使用相同的派生类型,就我的示例代码而言。 –

+0

@death.7你应该认识到,如果你不能改变已经从模板创建的'Create'的代码 - 那么就没有办法注入模拟。你必须在运行时做这样的事情。但是,这需要完全不同的设计。 – CouchDeveloper

0

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

// 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; 
}