Чтение данных в c++
Одна из распространённых задач при разработке программного обеспечения — это чтение данных из различных источников. В ходе написания крупного проекта, мне потребовалось извлекать информацию из нескольких источников. Однако источники данных могли быть двух типов: либо «объект строка», либо “файл”.
Возникает вопрос: как избежать необходимости проверять в программе типы источников данных и создать единый объект с универсальным интерфейсом, который будет работать с обоими типами?
В этой статье я представлю не всю реализацию. Полную версию можно найти на GitHub. Начнем с интерфейса. Мы создадим несколько методов, которые будут выполнять следующие функции:
- Проверка окончания данных;
- Получение текущего символа;
- Переход к следующему символу.
class Container{
public:
virtual ~Container() = default;
/** Проверка на то что данных больше нет */
virtual bool isEnd() = 0;
/** Текущий символ */
virtual char current() = 0;
/** Следующий символ */
virtual char next() = 0;
};
Этот класс содержит набор методов, которые должны быть реализованы в классах, наследуемых от него. Через него осуществляется доступ к этим методам. Реализация методов для чтения строк не представляет сложности:
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::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_;
}
Теперь, можно реализовать “фасад” для клиента. Для удобства использования я решил объединить все эти функции в одном объекте. Этот объект будет автоматически определять, откуда поступают данные, и использовать соответствующий объект для их обработки. Чтобы он понимал, с чем имеет дело, я создал специальный объект, который необходимо предварительно настроить и передать в конструктор класса:
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», теперь можно читать данные из файла или строки.
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;