Как избежать слишком большого количества операторов if-else при загрузке структуры из xml?
У меня есть следующий код (упрощенный) со многими операторами if-else. Он читает из XML и загружает значения в структуру:
static const QString ELT1("element1");
static const QString ELT2("element2");
static const QString ELT3("element3");
// ...
static const int ELT15("element15");
// .. up to 20 for now but it will grow later
структура такая:
struct Params {
QString param1;
QString param2;
// .. more QString's
int param15;
int param16;
// .. more integer's
};
а потом:
struct Params params;
while(!m_xml.atEnd()) {
m_xml.readNext();
if(m_xml.isStartElement()) {
QString name = m_xml.name();
if(name == ELT1)
readStringElement(params.param1);
else if(name == ELT2)
readStringElement(params.param2);
//...
else if(name == ELT15)
readIntegerElement(params.param15);
// .. till the end of the structure
}
}
Этот код пока работает, но xml будет расти, и слишком много операторов if-else сделает код более сложным для обслуживания.
Что может быть решением, чтобы сделать это более элегантным?
Спасибо.
2 ответа
Одним из решений является написание отдельной карты, которая определяет отношение между ELT#
константы и члены данных для чтения. Затем вы можете обратиться к этому отображению для отправки правильных вызовов функций с правильными аргументами. name
переменная, которую вы сравниваете в вашем if
Вместо этого s будет ключом для поиска на карте и соответствием value
будет std::function
который представляет собой правильный вызов функции, чтобы сделать для этого name
,
#include <functional>
#include <map>
#include <string>
// Types, functions and constants analogous to the ones in your question
struct Params
{
std::string param1;
int param2;
int param3;
};
void readStringElement(const std::string &);
void readIntegerElement(const int);
const std::string ELT1 = "element1";
const std::string ELT2 = "element2";
const std::string ELT3 = "element3";
// Param pointer map
const std::map<std::string, std::function<void(const Params &)>> param_map =
{
{ ELT1, [](const Params & param) { readStringElement(param.param1); } },
{ ELT2, [](const Params & param) { readIntegerElement(param.param2); } },
{ ELT3, [](const Params & param) { readIntegerElement(param.param3); } }
};
Использование будет выглядеть так:
#include <iostream>
void readStringElement(const std::string & param) {
std::cout << param << std::endl;
}
void readIntegerElement(const int param) {
std::cout << param << std::endl;
}
int main()
{
Params my_params = { "Hello, World!", 42, 123 };
param_map.at(ELT1)(my_params);
param_map.at(ELT2)(my_params);
param_map.at(ELT3)(my_params);
}
Хотя для каждого нового Param
член, вам все еще нужно определить новый ELT
константу и добавьте запись в карту самостоятельно. Это решение может помочь, но оно не устраняет все препятствия на пути сопровождения масштабируемого кода. Но поскольку в C++ нет отражения (невозможно автоматически выполнить итерацию всех членов типа без предварительного перечисления этих членов), и что ELT
константы нужно указывать индивидуально, чтобы предположительно соответствовать спецификации формата xml, я не вижу прямого решения этих оставшихся проблем.
Я бы лично использовал предложенный токен для отображения функций, что можно сделать так.
static std::map<const std::string, std::function<void(Params & params)> > elementToFuncMap{
{ELT1, [](Params & params){readStringElement(params.param1); },
{ELT2, [](Params & params){readIntegerElement(params.param2); },
.....
};
if(m_xml.isStartElement()) {
QString name = m_xml.name();
elementToFuncMap[name](params);
}
}