Чтение данных в c++

   Одна из распространённых задач при разработке программного обеспечения — это чтение данных из различных источников. В ходе написания крупного проекта, мне потребовалось извлекать информацию из нескольких источников. Однако источники данных могли быть двух типов: либо «объект строка», либо “файл”.

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

   В этой статье я представлю не всю реализацию. Полную версию можно найти на GitHub. Начнем с интерфейса. Мы создадим несколько методов, которые будут выполнять следующие функции:

  1. Проверка окончания данных;
  2. Получение текущего символа;
  3. Переход к следующему символу.

Container.hpp ->

class Container{
public:
    virtual ~Container() = default;
    /** Проверка на то что данных больше нет */
    virtual bool isEnd() = 0;
    /** Текущий символ */
    virtual char current() = 0;
    /** Следующий символ */
    virtual char next() = 0;
};

   Этот класс содержит набор методов, которые должны быть реализованы в классах, наследуемых от него. Через него осуществляется доступ к этим методам.    Реализация методов для чтения строк не представляет сложности:


ContainerStr.hpp ->

void ContainerStr::constructor_(){
    begin_ = container_.begin();
    end_ = container_.end();
    is_end_ = begin_ == end_;
    pos_ = 0;
}
/** Проверка на то что данных больше нет */
bool ContainerStr::isEnd(){
    auto begin = begin_;
    return is_end_ || ++begin == end_;
};

/** Текущий символ */
char ContainerStr::current(){
    return begin_[0];
}

/** Следующий символ */
char ContainerStr::next(){
    if(!is_end_){
    	begin_++;
    	pos_++;
        if(begin_ == end_){
            begin_--;
        	is_end_ = true;
        }
    }
    return begin_[0];
}

   Чтение файла имеет свои нюансы:


FileContainer.hpp ->

FileContainer::FileContainer(std::string_view file_name){
    file_p_.open(file_name.begin(), std::ios::binary);
    next_read_ = false;
    file_size_ = std::filesystem::file_size(file_name);
    next();
}

/** Проверка на то что данных больше нет */
bool FileContainer::isEnd(){
    return file_p_.is_open() && file_p_.eof();
}

/** Текущий символ */
char FileContainer::current(){
    return current_;
}

/** Следующий символ */
char FileContainer::next(){
    if(file_p_.is_open() && !file_p_.eof()){
        if(next_read_){
	    current_ = next_;
	}else{
	    file_p_.get(current_);
	}
	next_read_ = true;
	if(!file_p_.eof()){
	    file_p_.get(next_);
	}
    }else{
	current_ = next_;
    }
    return current_;
}

   Теперь, можно реализовать “фасад” для клиента. Для удобства использования я решил объединить все эти функции в одном объекте. Этот объект будет автоматически определять, откуда поступают данные, и использовать соответствующий объект для их обработки. Чтобы он понимал, с чем имеет дело, я создал специальный объект, который необходимо предварительно настроить и передать в конструктор класса:


ContentReader.hpp ->

struct ContentSettings{
    bool is_file, is_content;

    ContentSettings(){
	is_file = false;
	is_content = false;
    };
    ~ContentSettings(){};

    /** Инициализация для чтения файла */
    void setFileName(const std::string& file_name){
        is_file = true;
        is_content = false;
	file_name_ = file_name;
	content_.clear();
    }

    /** Инициализация для чтения строки */
    void setContent(const std::string& content){
	is_file = false;
	is_content = true;
	content_ = content;
	file_name_.clear();
    }
    std::string& getContent(){
	return content_;
    }
    std::string& getFileName(){
	return file_name_;
    }
private:
    std::string file_name_;
    std::string content_;
};



class ContentReader{

    std::shared_ptr<content_reader::Container> container_;
public:
    ContentReader(
    	ContentSettings contentSetting
    ){
	if(contentSetting.is_content){
            container_ = std::make_shared
                <content_reader::ContainerStr>(
                    std::move(contentSetting.getContent())
            );
        }else if(contentSetting.is_file){
            container_ = std::make_shared
                <content_reader::FileContainer>(
                    contentSetting.getFileName()
            );
        }
    };

    ~ContentReader(){};

    bool isEnd(){	
	return container_→isEnd();
    }
    char current(){
	return container_→current();
    }
    char next(){
	return container_→next();
    }
};

   С помощью класса «ContentReader», теперь можно читать данные из файла или строки.


main.cpp ->

ContentSettings setting;

setting.setFileName("test.txt"); 

ContentReader contentReaderFile(setting);
std::cout << contentReaderFile.current() << std::endl 
	<< contentReaderFile.next() 
<< std::endl;

setting.setContent("Test text for read content!");

ContentReader contentReaderStr(setting);
std::cout << contentReaderStr.current() << std::endl 
	<< contentReaderStr.next() 
<< std::endl;