Анализатор файлов, использующий стратегический шаблон — как получить результаты?

Допустим, я хочу создать анализатор файлов, который использует шаблон, подобный стратегии, чтобы разрешить использование различных специфических анализаторов, которые выполняли бы всю тяжелую работу, а затем возвращали результаты, которые могут различаться между ними.

Например, файл содержит A, B, C, D и E в каждой строке, и я хочу получить только A, C и E, поэтому я создаю специальный анализатор, который вытаскивает только их.

Я также должен отметить, что я имею дело с форматом NMEA, и каждый синтаксический анализатор будет обрабатывать различные операторы (строки) из файла.

class FileParser
{
std::string file_path;
public:
FileParser(std::string file_path, ??? parser);

void parseFile() {
// ...
// for each line:
parser.parseStatement(line);
// end for
// ...
}

??? getResults() {
return parser.getResults();
}
};

class Parser
{
public:
virtual void parseLine(std::string line);
??? getResults();
}

class SpecificParser : public Parser
{
void parseLine(std::string line);
SpecificFormat getResults();
}

И моя проблема — как правильно написать такой код?
Моей первой мыслью были шаблоны, но я пока не знаю, как их использовать, поэтому вторую мысль я получил:

FileParser otherFunction() {
ExampleParser ep();
FileParser fp("example.txt", &ep);
return fp;
}

void function() {
FileParser fp = otherFunction(); // countains reference to deleted instance of ExampleParser
}

… но это также глючит (я целенаправленно показал крайний случай).

Как правильно структурировать этот код?

Пример использования (как указано ниже)

auto parser1 = FileParser<SpecificParserType1>(file_name);
parser1.parseFile();
auto parser1.getResults(); // returns instance of specific type 1, e.g. std::vector<Type1>

auto parser2 = FileParser<SpecificParserType2>(file_name);
parser2.parseFile();
auto parser2.getResults(); // returns instance of specific type 2, e.g. std::vector<Type2>

Основная цель — иметь разные стратегии получения данных из файла.

1

Решение

С моей точки зрения, ваша идея использовать шаблон стратегии здесь неправильная, потому что:

Вы хотите создать иерархию классов, которая всегда около та же подпись, которая уже показывает вашу проблему: есть проблема дизайна, потому что ваш метод конкретной стратегии не вариант стратегии, потому что он имеет другой тип возврата.

Если такая вещь может быть реализована с помощью взлома, как указатели на что-нибудь вы сталкиваетесь со следующей проблемой. Итак, давайте определим, что все может быть уменьшено до data objects,

Ваши методы стратегии чтения конкретного анализатора файлов предоставляют огромное количество различных data object типы, которые должны быть описаны где-то в вашей программе для обработки таких. Подсказка: какой тип интерфейса будет data objects иметь? Может быть, вы хотите прочитать некоторые int значения, но также объекты std :: string, а также контейнерные классы. Есть ли общий интерфейс для этих типов?

Если у вас нет такого общего интерфейса для ваших типов данных, вам просто нужно нарушить общее правило разработки ООП: никогда не смотрите снаружи на детали внутри класса / объекта. Ваш обходной путь может быть что-то вроде:

switch ( dataobject[n].getType() )
{
case INT:
...;
case STRING:
...;

... and all the objects you have defined yourself!
}

Вы пишете сейчас код как:

auto parser1.getResults();

Ваш результат должен быть всегда одинаковым. auto не означает, что он может изменить тип во время выполнения. Так что это не решение здесь.

Следующая проблема:
Ваш программный код, кажется, делает некоторые предположения относительно содержимого вашего файла. Пользователь вашего шаблона стратегии должен знать, как объекты хранятся в файле, в каком конкретном порядке. Лучший способ состоит в том, что объект содержит немного контент и парсер могут справиться с этим. Но вот модель стратегии не правильная.

Мой намек:
Если ваш дизайн, независимый от какого-либо шаблона проектирования, приводит к определению иерархии классов, в которой есть некоторые виртуальные функции, которые должны иметь разные типы возвращаемых данных (что невозможно), ваш дизайн не работает!

1

Другие решения

Просто используйте наследование для вашего формата:

class Format{
public:
virtual ~Format(){}
virtual char *data() = 0;
};

class SpecificFormat: public Format{
public:
char *data(){return nullptr;}
};

Затем верните std::unique_ptr для тебя getResults функция:

class Parser{
public:
virtual std::unique_ptr<Format> get_results() = 0;
};

и в вашем конкретном классе:

class SpecificParser: public Parser{
public:
std::unique_ptr<Format> get_results(){
return std::make_unique<SpecificFormat>();
}
};

Тогда в вашем FileParser учебный класс:

class FileParser{
public:
FileParser(const std::string &file_path, std::unique_ptr<Parser> &&parser)
: parser(std::move(parser)){}

std::unique_ptr<Format> get_results(){
return parser->get_results();
}

private:
template<typename T>
struct TypeTag{};

template<typename T>
FileParser(const std::string &file_path, TypeTag<T>)
: parser(std::make_unique<T>()){}

template<typename T>
friend FileParser make_parser(const std::string &filepath);

std::unique_ptr<Parser> parser;
};

template<typename T>
FileParser make_parser(const std::string &filepath){
return FileParser(filepath, FileParser::TypeTag<T>{});
}

Конечно, вы бы вернули более заполненный Format детский класс.

Используйте это так:

int main(){
auto parser = FileParser{"test_file.my_format", std::make_unique<SpecificFormat>()};
auto results = parser.get_results();
auto data = results->data();
}

или же

int main(){
auto parser = make_parser<SpecificFormat>("testfile");
auto results = parser.get_results();
auto data = results->data();
}
1

Шаблоны, кажется, лучший способ для этого, так как вы знаете тип, который вы хотите получить от парсера. Поэтому вы можете сделать весь класс FileParser шаблоном и принять определенный класс синтаксического анализатора в качестве параметра шаблона.

Таким образом, вам не нужен один базовый класс для конкретных анализаторов, и вы можете получить правильный тип возврата для FileParser :: getResults (), используя синтаксис auto getResults () -> decltype (parser-> getResults ()) , В C ++ 14 вы даже можете написать просто «auto getResults ()» без части со стрелкой, и компилятор автоматически определит тип возвращаемого значения на основе тела функции.

template <typename Parser>
class FileParser
{
std::string file_path;
Parser parser;
public:
FileParser(std::string file_path, Parser parser): file_path(file_path), parser(parser) {}

void parseFile() {
// ...
// for each line:
parser.parseStatement(line);
// end for
// ...
}

auto getResults() -> decltype(parser.getResults()) {
return parser.getResults();
}
};

class SpecificParser
{
void parseStatement(std::string line);
SpecificFormat getResults();
}
1
По вопросам рекламы [email protected]