Would love some opinions on this problem I’m trying to workout. I’m trying to improve my OO experience and fully leverage C++’s polymorphic capabilities. I’m trying to write some code for a basic command parser. The commands comes from a Json multiple lines file.
As shown here:
{"command_name":"vkGetDeviceQueue","call_index":8,"threadId":3,"return_value":"void","parameters":{"device":"0x0000000000000005","queueFamilyIndex":0,"queueIndex":0,"pQueue":"0x0000000000000006"}}
{"command_name":"vkCreateFence","call_index":9,"threadId":3,"return_value":"VK_SUCCESS","parameters":{"device":"0x0000000000000005","pCreateInfo":{"sType":"VK_STRUCTURE_TYPE_FENCE_CREATE_INFO","flags":"0x00000001","pNext":null},"pAllocator":null,"pFence":"0x0000000000000010"}}
sample file of the JSON
here command_name can be lots of commands
What I’m doing now
if (commandName == "vkCreateShaderModule")
{
Parser<VulkanShaderModule> shaderModule;
shaderModule.dataStruct.captureID = captureID;
// shaderModule.pData = &dataZip;
shaderModule.Parse(data);
}
else if (commandName == "vkCreateGraphicsPipelines")
{
Parser<VulkanGraphicsPipeline> pipelineStruct;
pipelineStruct.dataStruct.captureID = captureID;
pipelineStruct.Parse(data);
}
As you know the API can have lots of API Calls. I would like to proceed on an OOP way.. probablly a factory pattern would help ? that’s my guess.. but don’t know how to proceed with a good design. I would like to avoid multiple if statements for each command in the full VULKAN API.
>Solution :
Ultimately you can’t really avoid if statements, but you can move this logic to e.g. a parser or a Factory if you prefer. Expanding a bit on john’s comment, you can have a Command class that looks more or less like this:
class Command
{
public:
virtual ~Command() = default;
void execute() = 0;
};
and some entity that allows you to create the superclass objects, like:
// or make it a factory class, if you feel it will be better
std::unique_ptr<Command> parseCommand(const SomeJson& j) { // here come your if/else logic
if (j["command_name"] == "command1")
{
// do some logic to create concrete command and return it; class can be defined in this translation unit, or somewhere else, up to you
return std::make_unique<CommandOne>(j["command_payload"]);
// return createCommand1(j["command_payload"]); // or move it to separate function, it can also be defined in this translation unit, or somewhere else, hidden from the user
}
// etc etc ...
}
where concrete CommandXxx classes can be hidden from your public interfaces. Then in your code you can have:
for (auto line : yourInputSplitInLines)
{
collection.push_back(parseCommand(SomeJson(line)));
}
// and later
for (auto& cmd : collection)
{
cmd->execute();
}
where collection is a container of your choice. It can also be std::unordered_map<std::string, std::unique_ptr<Command>> if you want to have the possibility to look up commands by name.