Я пишу заявку на C ++. Більшість додатків читають і записують цитування даних, і це не є винятком. Я створив дизайн високого рівня для моделі даних та логіки серіалізації. Це питання вимагає переглянути мій дизайн з урахуванням наступних конкретних цілей:
Мати простий та гнучкий спосіб зчитування та запису моделей даних у довільних форматах: бінарний сировина, 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, сокет і все, що завгодно. Після того, як ця проблема вкладається в логіку серіалізації, вона стає набагато складнішою: вона повинна знати конкретний тип потоку та якого конструктора викликати.
Окрім цього варіанту, я не впевнений, що був би кращим способом моделювання цих об’єктів, який є простим, гнучким та допомагає запобігти логічним помилкам у коді, який його використовує.
Випадок використання, з яким рішення має бути інтегровано, - це просте діалогове вікно вибору файлів . Користувач вибирає "Відкрити ..." або "Зберегти як ..." з меню Файл, і програма відкриває або зберігає базу даних WidgetData. Будуть також опції "Імпорт ..." та "Експорт ..." для окремих віджетів.
Коли користувач вибере файл для відкриття або збереження, wxWidgets поверне ім'я файлу. Обробник, який реагує на цю подію, повинен бути кодом загального призначення, який приймає ім'я файлу, отримує серіалізатор і викликає функцію для важкого підйому. В ідеалі ця конструкція також буде працювати, якщо інший фрагмент коду виконує нефайлові введення / виведення, наприклад, надсилання бази WidgetDatabase на мобільний пристрій через сокет.
Чи віджет зберігає у власному форматі? Чи взаємодіє він із існуючими форматами? Так! Все вищеперераховане. Повертаючись до діалогового вікна файлів, подумайте про Microsoft Word. Майкрософт міг вільно розробляти формат DOCX, однак хотів у певних обмеженнях. У той же час Word також читає або записує застарілі та сторонні формати (наприклад, PDF). Ця програма не відрізняється: "бінарний" формат, про який я говорю, - це ще визначений внутрішній формат, розроблений для швидкості. У той же час він повинен вміти читати та записувати відкриті стандартні формати у своїй області (не має значення для питання), щоб він міг працювати з іншим програмним забезпеченням.
Нарешті, є лише один тип віджету. У нього будуть дочірні об’єкти, але вони будуть оброблятися цією логікою серіалізації. Програма ніколи не завантажує і віджети, і зірочки. Цей проект тільки потрібно мати справу з віджетами і WidgetDatabases.