In making my first experiments with Abstract Factory in C++(while reading Modern C++ Design - A. Alexandrescu - part 9) i have a question. If classes hierarchy looks like this:
struct B {};
struct D1 :public B {};
struct DD1 : public D1 {};
struct DD2 : public D1 {};
struct D2 :public B {};
struct DD3 : public D2 {};
struct DD4 : public D2 {};
I have this abstract factory code:
//abstract factory version 1
struct AbstractFactoryImpl
{
virtual D1* CreateD1() = 0;
virtual D2* CreateD2() = 0;
};
struct AbstractFactory : public AbstractFactoryImpl
{
virtual D1* CreateD1() { return new D1; };
virtual D2* CreateD2() { return new D2; };
};
template<class ... Ts>
struct ConcreteFactory :public AbstractFactory
{
using params = std::tuple<Ts...>;
using T1 = typename std::tuple_element_t<0, params>;
using T2 = typename std::tuple_element_t<1, params>;
virtual D1* CreateD1()override
{
static_assert(std::is_base_of_v<D1, T1>);
return new T1;
};
virtual D2* CreateD2()override
{
static_assert(std::is_base_of_v<D2, T2>);
return new T2;
};
};
And using it in client code like this.
//version 1
AbstractFactory* pFactory;
pFactory = new ConcreteFactory<DD1, DD3>;
D1* pD1 = pFactory->CreateD1();
D2* pD2 = pFactory->CreateD2();
pFactory = new ConcreteFactory<DD2, DD4>;
pD1 = pFactory->CreateD1();
pD2 = pFactory->CreateD2();
It works what i want and looks like a big part of examples in internet.
But in this version of abstract factorys if i want to add more classes in heirarchy(D3,DD5,DD6...) i must create too much code manually so i want to make code more generic.
//abstract factory version 2
template<typename T>
struct AbstractFactoryImpl_
{
virtual T* Create() = 0;
};
template<typename T>
struct AbstractFactory_ :public AbstractFactoryImpl_<T>
{
virtual T* Create() override
{
return new T;
}
};
template<class ...Ts>
struct ConcreteFactory_ : public AbstractFactory_<Ts>...
{
using params = std::tuple<Ts...>;
using T1 = typename std::tuple_element_t<0, params>;
using T2 = typename std::tuple_element_t<1, params>;
template<class T>
T* Create()
{
if constexpr (std::is_base_of_v<T, T1>)return AbstractFactory_<T1>::Create();
else if constexpr (std::is_base_of_v<T, T2>)return AbstractFactory_<T2>::Create();
}
};
And it works in client code like this:
//version 2
//AbstractFactory* pFactory; -- can`t use because AbstractFactory in version 2 is template class.
auto pFactory_1 = new ConcreteFactory_<DD1, DD3>;
D1* pD1_ = pFactory_1->Create<D1>();
D2* pD2_ = pFactory_1->Create<D2>();
auto pFactory_2 = new ConcreteFactory_<DD2, DD4>;
pD1_ = pFactory_2->Create<D1>();
pD2_ = pFactory_2->Create<D2>();
So it works but i must create two different pointers(pFactory_1
,pFactory_2
) to different instances of ConcreteFactory_
. This is what not expected from Abstract Factory.
In first version it was ok because of virtual enheritance to call Create
on Base class pointer. But here i have template base class. So i cant call on it pointer Create()
.
So the question is how to make pointer or something else may be std::any o std::variant to make it possible in this Abstract Factory class design? The client code i wanna make to work is same like first version.
TYPE* pFactory_ = new ConcreteFactory_<DD1, DD3>;
D1* pD1_ = pFactory_->Create<D1>();
D2* pD2_ = pFactory_->Create<D2>();
auto pFactory_ = new ConcreteFactory_<DD2, DD4>;
pD1_ = pFactory_->Create<D1>();
pD2_ = pFactory_->Create<D2>();
Full code: [https://cppinsights.io/s/552bbe01]
With some changes you might have:
// Way to "pass" Type.
template <typename T> struct Tag{};
template <typename T>
struct AbstractFactory
{
virtual ~AbstractFactory() = default;
virtual std::unique_ptr<T> Create(Tag<T>) const = 0;
};
template <typename ... Ts>
struct AbstractFactories : virtual AbstractFactory<Ts>...
{
using AbstractFactory<Ts>::Create ...;
};
template <typename T, typename T2>
struct ConcreteFactory : virtual AbstractFactory<T>
{
std::unique_ptr<T> Create(Tag<T>) const override { return std::make_unique<T2>(); }
};
template <typename Base, typename ...Ts>
struct ConcreteFactories;
template <typename ... Ts1, typename ...Ts2>
struct ConcreteFactories<AbstractFactories <Ts1...>, Ts2...> : AbstractFactories <Ts1...>, ConcreteFactory<Ts1, Ts2>...
{
using ConcreteFactory<Ts1, Ts2>::Create ...;
};
With usage
void Test(AbstractFactories<D1, D2>& factory)
{
std::unique_ptr<D1> d1 = factory.Create(Tag<D1>{});
std::unique_ptr<D2> d2 = factory.Create(Tag<D2>{});
// ...
}
int main()
{
ConcreteFactories<AbstractFactories<D1, D2>, DD1, DD3> factory1;
ConcreteFactories<AbstractFactories<D1, D2>, DD2, DD4> factory2;
Test(factory1);
Test(factory2);
}