[英]C++ Perfect Generic Abstract Factory with arbitrary Constructor Arguments
對於單元測試,我正在嘗試創建一個滿足以下要求的工廠:
Timer
和TimerMock
)unique_ptr
到 Base class 到這些對象(例如unique_ptr<TimerInterface>
)目的是使用此工廠進行依賴注入,以便能夠將不屬於測試的對象與模擬對象交換。
這是我到目前為止所擁有的:
#include <memory>
#include <iostream>
//"perfect factory method"
//usage: std::unique_ptr<Type> pt(create<Type>(Type-constructor-arguments));
template <typename Type, typename ... ConstructorArgs>
auto create(ConstructorArgs&& ... args){
return std::make_unique<Type>(std::forward<ConstructorArgs>(args)...);
}
//Abstract Factory Base class
template<typename BaseType, typename ... ConstructorArgs>
class IFactory {
public:
virtual ~IFactory() = default;
virtual std::unique_ptr<BaseType> create(ConstructorArgs&& ... args) const = 0;
};
//Abstract Factory class
template <typename BaseType, typename DerivedType, typename ... ConstructorArgs>
class CFactory : public IFactory<BaseType, ConstructorArgs...>
{
public:
using Base = IFactory<BaseType, ConstructorArgs...>;
std::unique_ptr<BaseType> create(ConstructorArgs&& ... args) const override
{
return ::create<DerivedType>(std::forward<ConstructorArgs>(args)...);
}
};
真正的工廠類是如何定義的:
class TimerInterface {
public:
TimerInterface() = default;
TimerInterface (const char* name);
virtual void whoami() const = 0;
/*...*/
};
class Timer: public TimerInterface {
public:
Timer() = default;
Timer(const char* name) : TimerInterface (name) {}
void whoami() const override { std::cerr << "I'm real!" << std::endl; }
/*...*/
};
class TimerMock : public TimerInterface {
public:
TimerMock () = default;
TimerMock (const char* name) : TimerInterface (name) {}
void whoami() const override { std::cerr << "I fake it!" << std::endl; }
/*...*/
};
using TimerFactory = CFactory<TimerInterface, Timer, const char*>;
using TimerMockFactory = CFactory<TimerInterface, TimerMock, const char*>;
using TimerFactoryInterface = TimerFactory::Base;
以及它們的用途:
class ClassUnderTest {
public:
std::unique_ptr<TimerInterface> timer {};
std::unique_ptr<TimerInterface> timer2 {};
ClassUnderTest(const TimerFactoryInterface& factory)
: timer(factory.create("I got a name!"))
//, timer2(factory.create())
{}
};
class Production
{
public:
ClassUnderTest realUsage;
Production() :
realUsage(TimerFactory())
{}
};
class Test
{
public:
ClassUnderTest tested;
Test() :
tested(TimerMockFactory())
{}
};
int main()
{
Production p;
p.realUsage.timer->whoami();
Test t;
t.tested.timer->whoami();
}
我的大問題是要求 (4) ClassUnderTest::timer2 不能使用與 ClassUnderTest::timer 相同的工廠創建,因為在定義 CFactory class 時已經需要知道構造函數簽名。
有人有想法嗎?
PS:帶有解釋的“無法完成”也是可以接受的答案,但不是我最喜歡的;)
不確定它是否有幫助,但是當您在ClassUnderTest
中聲明兩個std::unique_ptr<TimerInterface>
時, TimerInterface
不需要是完整的類型。 意味着這是合法的:
// classundertest.hpp
// A forward-declaration is enough
class TimerInterface;
class TimerFactoryInterface;
class ClassUnderTest {
public:
std::unique_ptr<TimerInterface> timer {};
std::unique_ptr<TimerInterface> timer2 {};
// Both MUST be declared here AND defined in the .cpp for this trick to work
ClassUnderTest(const TimerFactoryInterface& factory);
~ClassUnderTest();
// classundertest.cpp
#include "classundertest.hpp"
// TimerInterface wasn't truly declared until now
#include "TimerInterface.hpp"
ClassUnderTest::ClassUnderTest(const TimerFactoryInterface& factory)
: timer(factory.create("I got a name!"))
, timer2(factory.create("???"))
{}
ClassUnderTest::~ClassUnderTest()
{}
這基本上就是基於 unique_ptr 的pimpl的工作方式。
您可以使用std::vector<std::any>
和一些索引序列來做到這一點,但這不會是最漂亮或最快速的解決方案。
您的工廠將是:
template<typename Base>
class IFactory
{
public:
virtual std::unique_ptr<Base> Create(const std::vector<std::any>& args) const = 0;
};
您的具體工廠可能是:
template<typename Base, typename Derived, typename... Args>
class CFactory : IFactory<Base>
{
private:
template<size_t... Indices>
std::unique_ptr<Base> Make(const std::vector<std::any>& args, std::index_sequence<Indices...>) const
{
return std::make_unique<Derived>(std::any_cast<Args>(args[Indices])...);
}
public:
virtual std::unique_ptr<Base> Create(const std::vector<std::any>& args) const
{
return Make(args, std::make_index_sequence<sizeof...(Args)>());
}
};
這里唯一的問題是你顯然不能完美地轉發任何 arguments,你總是會得到一份副本。 如果傳入std::any
::any 的類型與模板不匹配,它也會拋出std::bad_any_cast
。
這是我做的一個簡單測試,它使用 MSVC 和 Clang 編譯,並設置了-std=c++17
:
class A
{
public:
A(int a, int b)
: Val1(a), Val2(b)
{}
A(int a)
: Val1(a), Val2(0)
{}
int Val1, Val2;
};
int main()
{
CFactory<A, A, int> fac1;
CFactory<A, A, int, int> fac2;
auto a1 = fac1.Create({ std::any(10) });
auto a2 = fac2.Create({ std::any(10), std::any(20) });
std::cout << a1->Val1 << " " << a1->Val2 << "\n";
std::cout << a2->Val1 << " " << a2->Val2 << "\n";
}
編輯:這將適用於重載的構造函數,以及任何類型,因為它的模板魔法。
@SparkyPotato 在聲明我“必須手動列出可能的構造函數並為創建生成重載”時給了我這個想法。 我只是不喜歡手動,所以我通過模板元編程來做到這一點。 感謝您的提示!
#include <memory>
#include <iostream>
#include <tuple>
//"perfect factory method"
//usage: std::unique_ptr<Type> pt(create<Type>(Type-constructor-arguments));
template <typename Type, typename ... ConstructorArgs>
auto create(ConstructorArgs&& ... args){
std::cerr << "calling " << __PRETTY_FUNCTION__ << std::endl;
return std::make_unique<Type>(std::forward<ConstructorArgs>(args)...);
}
//Abstract Factory Variadic class. This is also generic case that ends recursion
template <typename BaseType, typename TupleListOfConstructorArgs>
class IFactory
{
static_assert(std::is_same<TupleListOfConstructorArgs, std::tuple<>>::value, "");
public:
//this method shall never be instatiated, it just exists to satisfy the "using BaseFactory::create" in IFactory
template <typename T> void create(){ static_assert(sizeof(BaseType) + sizeof(T) < 0, ""); }
virtual ~IFactory() = default;
};
//Abstract Factory Variadic class specialization to perform inheritance recursion
template <typename BaseType, typename ... CurrentTupleArgs, typename ... TupleListOfConstructorArgs>
class IFactory<BaseType, std::tuple<std::tuple<CurrentTupleArgs...>, TupleListOfConstructorArgs...>> : public IFactory<BaseType, std::tuple<TupleListOfConstructorArgs...>>
{
public:
using BaseFactory = IFactory<BaseType, std::tuple<TupleListOfConstructorArgs...>>;
using BaseFactory::create;
virtual std::unique_ptr<BaseType> create(const CurrentTupleArgs& ... args) const = 0;
};
//Concrete Factory Variadic class. This is also generic case that ends inheritance recursion
template <typename BaseType, typename DerivedType, typename TupleListOfConstructorArgs, typename FullTupleListOfConstructorArgs>
class CFactory : public IFactory<BaseType, FullTupleListOfConstructorArgs>
{
static_assert(std::is_same<TupleListOfConstructorArgs, std::tuple<>>::value, "");
public:
using Base = IFactory<BaseType, FullTupleListOfConstructorArgs>;
};
//Concrete Factory Variadic class specialization to perform inheritance recursion
template <typename BaseType, typename DerivedType, typename ... CurrentTupleArgs, typename ... TupleListOfConstructorArgs, typename FullTupleListOfConstructorArgs>
class CFactory<BaseType, DerivedType, std::tuple<std::tuple<CurrentTupleArgs...>, TupleListOfConstructorArgs...>, FullTupleListOfConstructorArgs> : public CFactory<BaseType, DerivedType, std::tuple<TupleListOfConstructorArgs...>, FullTupleListOfConstructorArgs>
{
public:
using BaseFactory = CFactory<BaseType, DerivedType, std::tuple<TupleListOfConstructorArgs...>, FullTupleListOfConstructorArgs>;
using BaseFactory::create;
std::unique_ptr<BaseType> create(const CurrentTupleArgs& ... args) const override
{
std::cerr << "calling " << __PRETTY_FUNCTION__ << std::endl;
return ::create<DerivedType>(args...);
}
};
template <typename BaseType, typename DerivedType, typename TupleListOfConstructorArgs>
using CFactoryFrontend = CFactory<BaseType, DerivedType, TupleListOfConstructorArgs, TupleListOfConstructorArgs>;
class TimerInterface {
public:
TimerInterface() = default;
virtual ~TimerInterface() = default;
TimerInterface (const char* name) {}
TimerInterface(int& x, const char* name) { std::cerr << "calling " << __PRETTY_FUNCTION__ << std::endl; }
TimerInterface(const int& x, const char* name) { std::cerr << "calling " << __PRETTY_FUNCTION__ << std::endl; }
virtual void whoami() const = 0;
/*...*/
};
class Timer: public TimerInterface {
public:
Timer() = default;
Timer(const char* name) : TimerInterface (name) {}
Timer(int& x, const char* name) : TimerInterface(x, name) {}
Timer(const int& x, const char* name) : TimerInterface(x, name) {}
void whoami() const override { std::cerr << "I'm real!" << std::endl; }
/*...*/
};
class TimerMock : public TimerInterface {
public:
TimerMock () = default;
TimerMock (const char* name) : TimerInterface (name) {}
TimerMock (int& x, const char* name) : TimerInterface(x, name) {}
TimerMock (const int& x, const char* name) : TimerInterface(x, name) {}
void whoami() const override { std::cerr << "I fake it!" << std::endl; }
/*...*/
};
//using TimerInterfaceConstructors = std::tuple<std::tuple<>, std::tuple<const char*>>;
using Constructors = std::tuple<std::tuple<>, std::tuple<const char*>, std::tuple<int&, const char*>, std::tuple<const int&, const char*>>;
using TestFactory = CFactoryFrontend<TimerInterface, Timer, Constructors>;
using TimerFactory = CFactoryFrontend<TimerInterface, Timer, Constructors>;
using TimerMockFactory = CFactoryFrontend<TimerInterface, TimerMock, Constructors>;
using TimerFactoryInterface = TimerFactory::Base;
class ClassUnderTest {
public:
std::unique_ptr<TimerInterface> timer {};
std::unique_ptr<TimerInterface> timer2 {};
ClassUnderTest(const TimerFactoryInterface& factory)
: timer(factory.create("I got a name!"))
, timer2(factory.create())
{}
};
class Production
{
public:
ClassUnderTest realUsage;
Production() :
realUsage(TimerFactory())
{}
};
class Test
{
public:
ClassUnderTest tested;
Test() :
tested(TimerMockFactory())
{}
};
int main()
{
Production p;
p.realUsage.timer->whoami();
Test t;
t.tested.timer->whoami();
TestFactory tf;
TimerFactoryInterface& tfi(tf);
const char* x = "X";
tfi.create();
tfi.create(x);
int y;
const int cy = 17;
tfi.create(y, x);
tfi.create(cy, "name");
::create<Timer>(x);
}
它使用 GCC-6 和更新版本以及 clang 和 -std=c++-14 進行編譯
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.