Как избежать слишком большого количества операторов 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);

    }
}
Другие вопросы по тегам