Я пишу приложение на C ++. Большинству приложений требуется чтение и запись данных, и это не исключение. Я создал дизайн высокого уровня для модели данных и логики сериализации. Этот вопрос требует пересмотра моего дизайна с учетом этих конкретных целей:
Иметь простой и гибкий способ чтения и записи моделей данных в произвольных форматах: raw двоичный, XML, JSON и др. и др. Формат данных должен быть отделен от самих данных, а также от кода, который запрашивает сериализацию.
Чтобы обеспечить максимально безошибочную сериализацию. Ввод / вывод по своей природе сопряжен с риском по ряду причин: вводит ли мой дизайн больше способов его провала? Если так, как я мог бы реорганизовать дизайн, чтобы уменьшить эти риски?
Этот проект использует C ++. Любите ли вы это или ненавидите, у языка есть свой собственный способ действий, и дизайн нацелен на работу с языком, а не против него .
Наконец, проект построен поверх wxWidgets . Хотя я ищу решение, применимое к более общему случаю, эта конкретная реализация должна прекрасно работать с этим инструментарием.
Далее следует очень простой набор классов, написанных на C ++, которые иллюстрируют дизайн. Это не те классы, которые я частично написал до сих пор, этот код просто иллюстрирует дизайн, который я использую.
Во-первых, некоторые примеры DAO:
#include <iostream>
#include <map>
#include <memory>
#include <string>
#include <vector>
// One widget represents one record in the application.
class Widget {
public:
using id_type = int;
private:
id_type id;
};
// Container for widgets. Much more than a dumb container,
// it will also have indexes and other metadata. This represents
// one data file the user may open in the application.
class WidgetDatabase {
::std::map<Widget::id_type, ::std::shared_ptr<Widget>> widgets;
};
Далее я определяю чисто виртуальные классы (интерфейсы) для чтения и записи DAO. Идея состоит в том, чтобы абстрагировать сериализацию данных от самих данных ( SRP ).
class WidgetReader {
public:
virtual Widget read(::std::istream &in) const abstract;
};
class WidgetWriter {
public:
virtual void write(::std::ostream &out, const Widget &widget) const abstract;
};
class WidgetDatabaseReader {
public:
virtual WidgetDatabase read(::std::istream &in) const abstract;
};
class WidgetDatabaseWriter {
public:
virtual void write(::std::ostream &out, const WidgetDatabase &widgetDb) const abstract;
};
Наконец, вот код, который получает правильное устройство чтения / записи для нужного типа ввода / вывода. Также будут определены подклассы читателей / писателей, но они ничего не добавляют к обзору проекта:
enum class WidgetIoType {
BINARY,
JSON,
XML
// Other types TBD.
};
WidgetIoType forFilename(::std::string &name) { return ...; }
class WidgetIoFactory {
public:
static ::std::unique_ptr<WidgetReader> getWidgetReader(const WidgetIoType &type) {
return ::std::unique_ptr<WidgetReader>(/* TODO */);
}
static ::std::unique_ptr<WidgetWriter> getWidgetWriter(const WidgetIoType &type) {
return ::std::unique_ptr<WidgetWriter>(/* TODO */);
}
static ::std::unique_ptr<WidgetDatabaseReader> getWidgetDatabaseReader(const WidgetIoType &type) {
return ::std::unique_ptr<WidgetDatabaseReader>(/* TODO */);
}
static ::std::unique_ptr<WidgetDatabaseWriter> getWidgetDatabaseWriter(const WidgetIoType &type) {
return ::std::unique_ptr<WidgetDatabaseWriter>(/* TODO */);
}
};
В соответствии с заявленными целями моего дизайна, у меня есть одна конкретная проблема. Потоки C ++ могут быть открыты в текстовом или двоичном режиме, но нет способа проверить уже открытый поток. Возможно из-за ошибки программиста предоставить, например, двоичный поток для чтения / записи XML или JSON. Это может вызвать тонкие (или не очень) ошибки. Я бы предпочел, чтобы код не работал быстро, но я не уверен, что этот дизайн сделает это.
Одним из способов решения этой проблемы может быть снятие ответственности за открытие потока для читателя или автора, но я считаю, что это нарушает SRP и сделает код более сложным. При написании DAO автору записи не нужно заботиться о том, куда движется поток: это может быть файл, стандартный вывод, ответ HTTP, сокет, что угодно. Как только эта проблема инкапсулируется в логике сериализации, она становится гораздо более сложной: она должна знать конкретный тип потока и какой конструктор вызывать.
Помимо этой опции, я не уверен, что будет лучшим способом моделирования этих объектов, который будет простым, гибким и поможет предотвратить логические ошибки в коде, который его использует.
Вариант использования, с которым необходимо интегрировать решение, - это простое диалоговое окно выбора файла . Пользователь выбирает «Открыть ...» или «Сохранить как ...» в меню «Файл», и программа открывает или сохраняет базу данных WidgetDatabase. Также будут доступны опции «Импорт ...» и «Экспорт ...» для отдельных виджетов.
Когда пользователь выбирает файл для открытия или сохранения, wxWidgets возвращает имя файла. Обработчик, который отвечает на это событие, должен быть кодом общего назначения, который принимает имя файла, получает сериализатор и вызывает функцию для выполнения тяжелой работы. В идеале этот дизайн также будет работать, если другой фрагмент кода выполняет нефайловый ввод / вывод, например, отправку базы данных WidgetDD на мобильное устройство через сокет.
Сохраняет ли виджет свой собственный формат? Взаимодействует ли он с существующими форматами? Да! Все вышеперечисленное. Возвращаясь к диалогу файла, подумайте о Microsoft Word. Microsoft была свободна в разработке формата DOCX, однако они хотели в рамках определенных ограничений. В то же время Word также читает или пишет устаревшие и сторонние форматы (например, PDF). Эта программа ничем не отличается: «двоичный» формат, о котором я говорю, - это еще не определенный внутренний формат, разработанный для скорости. В то же время он должен уметь читать и записывать открытые стандартные форматы в своей области (независимо от вопроса), чтобы он мог работать с другим программным обеспечением.
Наконец, есть только один тип виджетов. У него будут дочерние объекты, но они будут обрабатываться этой логикой сериализации. Программа никогда не загрузит как виджеты, так и звездочки. Этот проект только нужно иметь дело с виджетами и WidgetDatabases.