Mam klasę Node i różne deskryptory dla tych węzłów.
deskryptory rozróżnia się przez std::string category
:
-
Kategoria `Math': AddNode, MultiplyNode
-
Kategoria 'Parameter: FloatNode, IntNode
category
jest typu string, ponieważ chcę, aby mój edytor wyświetlał listę kategorii, a pod każdą kategorią nazwe tego deskryptory, np.
-Math
- Add
- Multiply
-Parameter
- Float
- Int
Zastanawiam się, jaki jest najlepszy sposób na utworzenie konkretnego skategoryzowanego węzła.
Mam do przejrzenia dwie implementacje
Te implementacje mają wspólne ~40 pierwszych linii kodu:
#include <string>
#include <string_view>
#include <map>
#include <functional>
#include <iostream>
#include <utility>
#include <vector>
#include <memory>
struct Node {
Node(std::string_view text)
: text(text) {}
void Execute() {
std::cout << text << "\n";
}
private:
std::string text;
};
//Math Nodes descriptors
struct AddNode {
static constexpr std::string_view getText() { return "add"; }
};
struct MultiplyNode {
static constexpr std::string_view getText() { return "multiply"; }
};
//Math Nodes descriptors
//parameter Nodes descriptors
struct FloatNode {
static constexpr std::string_view getText() { return "float"; }
};
struct IntNode {
static constexpr std::string_view getText() { return "int"; }
};
//parameter Nodes descriptors
Pierwsza implementacja, prosta fabryka:
struct NodeFactory {
using Registry = std::map<std::string, std::map<std::string, std::function<std::unique_ptr<Node>()>>>;
NodeFactory() {
RegisterMathNodes();
RegisterParameterNodes();
}
~NodeFactory() = default;
NodeFactory(NodeFactory&) = delete;
NodeFactory& operator=(NodeFactory&) = delete;
NodeFactory(NodeFactory&&) = delete;
NodeFactory& operator=(NodeFactory&&) = delete;
[[nodiscard]] std::unique_ptr<Node> CreateNode(const std::string& category, const std::string& type) {
if(registry.contains(category)) {
auto& concreteRegistry = registry[category];
if(concreteRegistry.contains(type)) {
return std::move(concreteRegistry[type]());
}
return nullptr;
}
return nullptr;
}
private:
template<typename NodeT>
[[nodiscard]] static std::function<std::unique_ptr<Node>()> CreateNode()
{
return [](){ return std::make_unique<Node>(NodeT::getText()); };
}
#define REGISTER(container, NodeT) container[#NodeT] = CreateNode<NodeT>()
void RegisterMathNodes() {
auto& mathRegistry = registry["Math"];
REGISTER(mathRegistry, AddNode);
REGISTER(mathRegistry, MultiplyNode);
}
void RegisterParameterNodes() {
auto& parameterRegistry = registry["Parameter"];
REGISTER(parameterRegistry, FloatNode);
REGISTER(parameterRegistry, IntNode);
}
Registry registry;
};
int main() {
NodeFactory factory;
std::vector<std::shared_ptr<Node>> nodes;
nodes.push_back(factory.CreateNode("Math", "AddNode"));
nodes.push_back(factory.CreateNode("Parameter", "FloatNode"));
nodes.push_back(factory.CreateNode("Math", "MultiplyNode"));
nodes.push_back(factory.CreateNode("Math", "AddNode"));
for(const auto& node : nodes) {
node->Execute();
}
return 0;
}
druga implementacja, każda kategoria ma osobną abryke:
struct TNodeFactory {
using Registry = std::map<std::string, std::map<std::string, std::function<std::unique_ptr<Node>()>>>;
friend struct AbstractNodeFactory;
TNodeFactory() = default;
TNodeFactory(TNodeFactory&) = delete;
TNodeFactory& operator=(TNodeFactory&) = delete;
TNodeFactory(TNodeFactory&&) = delete;
TNodeFactory& operator=(TNodeFactory&&) = delete;
virtual ~TNodeFactory() = default;
[[nodiscard]] std::unique_ptr<Node> CreateNode(const std::string& type) {
const std::string& category = getCategory();
if(registry.contains(category)) {
auto& concreteRegistry = registry[category];
if(concreteRegistry.contains(type)) {
return std::move(concreteRegistry[type]());
}
return nullptr;
}
return nullptr;
}
protected:
[[nodiscard]] virtual std::string getCategory() = 0;
Registry registry;
};
namespace Internal
{
template<typename NodeT>
[[nodiscard]] static std::function<std::unique_ptr<Node>()> CreateNode()
{
return [](){ return std::make_unique<Node>(NodeT::getText()); };
}
}
#define REGISTER(container, NodeT) container[#NodeT] = Internal::CreateNode<NodeT>()
struct MathNodeFactory : virtual TNodeFactory
{
static constexpr std::string category = "Math";
MathNodeFactory() {
auto& mathRegistry = registry[getCategory()];
REGISTER(mathRegistry, AddNode);
REGISTER(mathRegistry, MultiplyNode);
}
~MathNodeFactory() override = default;
private:
std::string getCategory() override { return category; }
};
struct ParameterNodeFactory : virtual TNodeFactory
{
static constexpr std::string category = "Parameter";
ParameterNodeFactory() {
auto& parameterRegistry = registry[getCategory()];
REGISTER(parameterRegistry, FloatNode);
REGISTER(parameterRegistry, IntNode);
}
~ParameterNodeFactory() override = default;
private:
std::string getCategory() override { return category; }
};
struct AbstractNodeFactory {
AbstractNodeFactory()
{
factoriesRegistry[MathNodeFactory::category] = std::make_unique<MathNodeFactory>();
factoriesRegistry[ParameterNodeFactory::category] = std::make_unique<ParameterNodeFactory>();
}
~AbstractNodeFactory() = default;
AbstractNodeFactory(AbstractNodeFactory&) = delete;
AbstractNodeFactory& operator=(AbstractNodeFactory&) = delete;
AbstractNodeFactory(AbstractNodeFactory&&) = delete;
AbstractNodeFactory& operator=(AbstractNodeFactory&&) = delete;
[[nodiscard]] std::unique_ptr<Node> CreateNode(const std::string& category, const std::string& type) {
if(factoriesRegistry.contains(category)) {
auto concreteFactory = factoriesRegistry[category].get();
return concreteFactory->CreateNode(type);
}
return nullptr;
}
private:
std::map<std::string, std::unique_ptr<TNodeFactory>> factoriesRegistry;
};
int main() {
AbstractNodeFactory factory;
std::vector<std::shared_ptr<Node>> nodes;
nodes.push_back(factory.CreateNode("Math", "AddNode"));
nodes.push_back(factory.CreateNode("Parameter", "FloatNode"));
nodes.push_back(factory.CreateNode("Math", "MultiplyNode"));
nodes.push_back(factory.CreateNode("Math", "AddNode"));
for(const auto& node : nodes) {
node->Execute();
}
return 0;
}
https://godbolt.org/z/Wf1vvn9Px
Funkcja CreateNode
jest tylko minimalnym przykładem, a rzeczywista implementacja jest bardziej skomplikowana. Chciałbym się tutaj tylko skupić nad wyborem odpowiedniego wzorca.
Której implementacji powinienem użyć? Czy może, w ogóle źle podszedłem do tematu i powinienem inaczej to zaimplementować?