Допустим, я хочу создать анализатор файлов, который использует шаблон, подобный стратегии, чтобы разрешить использование различных специфических анализаторов, которые выполняли бы всю тяжелую работу, а затем возвращали результаты, которые могут различаться между ними.
Например, файл содержит 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>
Основная цель — иметь разные стратегии получения данных из файла.
С моей точки зрения, ваша идея использовать шаблон стратегии здесь неправильная, потому что:
Вы хотите создать иерархию классов, которая всегда около та же подпись, которая уже показывает вашу проблему: есть проблема дизайна, потому что ваш метод конкретной стратегии не вариант стратегии, потому что он имеет другой тип возврата.
Если такая вещь может быть реализована с помощью взлома, как указатели на что-нибудь вы сталкиваетесь со следующей проблемой. Итак, давайте определим, что все может быть уменьшено до 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 не означает, что он может изменить тип во время выполнения. Так что это не решение здесь.
Следующая проблема:
Ваш программный код, кажется, делает некоторые предположения относительно содержимого вашего файла. Пользователь вашего шаблона стратегии должен знать, как объекты хранятся в файле, в каком конкретном порядке. Лучший способ состоит в том, что объект содержит немного контент и парсер могут справиться с этим. Но вот модель стратегии не правильная.
Мой намек:
Если ваш дизайн, независимый от какого-либо шаблона проектирования, приводит к определению иерархии классов, в которой есть некоторые виртуальные функции, которые должны иметь разные типы возвращаемых данных (что невозможно), ваш дизайн не работает!
Просто используйте наследование для вашего формата:
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();
}
Шаблоны, кажется, лучший способ для этого, так как вы знаете тип, который вы хотите получить от парсера. Поэтому вы можете сделать весь класс 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();
}