我有一个基类,为了便于论证,我们将其称为
Node
,它将具有任意数量的叶实现。现在我们将其限制为两个:NodeA
和 NodeB
。它们需要不同的参数来构建。
我想创建一个节点,并且我有一个用于此目的的工厂类。工厂有模板功能
template<typename NodeType, typename...Args>
Node * createNode(Args&&... args)
{
auto const node = new NodeType(std::forward(args)...);
/* other stuff*/
return node;
}
这允许我创建新的节点类型,而不必担心向工厂添加额外的创建函数
auto const nodeA = factory->createNode<NodeA>("foo", 10);
auto const nodeB = factory->createNode<NodeB>("bar", "wibble", 100.0f);
但这里有一个转折点:返回的 actual 类型不仅取决于 NodeType 参数,还取决于工厂类型。
class NodeA1 : public NodeA {/*stuff*/};
class Factory1 : public Factory
{
/* C++ won't allow me to virtualise (or overload?) createNode*/
template<typename NodeType, typename...Args>
Node * createNode(Args&&...args)
{
/* If NodeType is A, I want to return new NodeA1() */
???
}
};
我不想有这样的界面
class Factory
{
virtual Node * createNodeA(/*A args*/) = 0;
virtual Node * createNodeB(/*B args*/) = 0;
};
因为扩展性很差(每个工厂类型都会受到参数更改的影响,并且必须扩展以创建新类型)。我担心我可能不可避免地必须这样做,但想知道是否有更干净的方法来实现这种设计?或者确实是更好的设计!
典型的方法是使用类型擦除,使用的类型擦除量取决于需要扩展的内容和具体内容。
由于
createNode
是模板化的,它不能是虚拟的,它能做的最好的事情就是将其参数转换为类型擦除的对象,然后将其传递给虚拟函数,我们称之为 createNode_impl
。
每个工厂将使用这个类型擦除的对象来解析所需的类型(NodeA、NodeB 等),然后构造自己的版本(NodeA1 等)
#include <iostream>
#include <string_view>
#include <memory>
struct Node {
Node() = default;
Node(Node&&) = default;
Node(const Node&) = default;
Node& operator=(Node&&) = default;
Node& operator=(const Node&) = default;
virtual ~Node() = default;
virtual void foo() = 0;
};
struct ConstructionArgs
{
virtual std::string_view name() = 0;
};
struct NodeAConstructionArgs : public ConstructionArgs
{
std::string_view name() override
{
return "NodeA";
}
std::string data = "some_data";
};
struct NodeA : public Node
{
NodeA(std::string s) : data{ std::move(s) } {}
std::string data;
};
struct AbstractFactory
{
template <typename T, typename...Args>
std::unique_ptr<Node> createNode(Args&&...args)
{
if constexpr (std::same_as<T, NodeA>)
{
auto arg = NodeAConstructionArgs{ args... };
return Construct_impl(arg);
}
// others
return nullptr;
}
virtual std::unique_ptr<Node> createNode_impl(ConstructionArgs& args) = 0;
virtual ~AbstractFactory() = default;
};
struct NodeA1 : public NodeA
{
using NodeA::NodeA;
virtual void foo() { std::cout << "A1"; }
};
struct Factory1 : public AbstractFactory
{
std::unique_ptr<Node> createNode_impl(ConstructionArgs& args) override
{
if (auto* obj = dynamic_cast<NodeAConstructionArgs*>(&args))
{
return std::make_unique<NodeA1>(NodeA1{ std::move(obj->data) });
}
return nullptr;
}
};
int main()
{
Factory1 f;
auto obj = f.Construct<NodeA>();
obj->foo();
}