Piszę aplikację C ++. Większość aplikacji odczytuje i zapisuje dane potrzebne do cytowania, a ten nie jest wyjątkiem. Stworzyłem projekt wysokiego poziomu dla modelu danych i logiki serializacji. To pytanie wymaga przeglądu mojego projektu z uwzględnieniem tych konkretnych celów:
Aby mieć łatwy i elastyczny sposób odczytywania i zapisywania modeli danych w dowolnych formatach: raw binary, XML, JSON i in. glin. Format danych należy oddzielić od samych danych, a także od kodu żądającego serializacji.
Aby zapewnić, że serializacja jest możliwie bezbłędna. We / wy jest z natury ryzykowne z różnych powodów: czy mój projekt wprowadza więcej sposobów na awarię? Jeśli tak, to w jaki sposób mogę zmienić projekt, aby ograniczyć to ryzyko?
Ten projekt używa C ++. Niezależnie od tego, czy go kochasz, czy nienawidzisz, język ma swój własny sposób robienia rzeczy, a projekt ma na celu współpracę z językiem, a nie przeciwko niemu .
Wreszcie projekt jest oparty na wxWidgets . Podczas gdy szukam rozwiązania dla bardziej ogólnego przypadku, ta konkretna implementacja powinna dobrze działać z tym zestawem narzędzi.
Poniżej znajduje się bardzo prosty zestaw klas napisanych w C ++, które ilustrują projekt. To nie są rzeczywiste klasy, które do tej pory napisałem częściowo, ten kod po prostu ilustruje projekt, którego używam.
Po pierwsze, niektóre przykładowe 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;
};
Następnie definiuję czyste wirtualne klasy (interfejsy) do odczytu i zapisu DAO. Chodzi o to, aby wyodrębnić serializację danych z samych danych ( 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;
};
Wreszcie, oto kod, który pobiera odpowiedni czytnik / moduł zapisujący dla żądanego typu wejścia / wyjścia. Zdefiniowano by również podklasy czytelników / pisarzy, ale nie dodają one niczego do recenzji projektu:
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 */);
}
};
Zgodnie z określonymi celami mojego projektu mam jeden szczególny problem. Strumienie C ++ można otwierać w trybie tekstowym lub binarnym, ale nie ma możliwości sprawdzenia już otwartego strumienia. Błąd programisty może umożliwiać np. Dostarczenie strumienia binarnego do czytnika / zapisu XML lub JSON. Może to powodować subtelne (lub nie tak subtelne) błędy. Wolałbym, żeby kod szybko zawodził, ale nie jestem pewien, czy ten projekt by to zrobił.
Jednym z rozwiązań może być odciążenie odpowiedzialności za otwarcie strumienia dla czytelnika lub pisarza, ale uważam, że narusza to SRP i uczyniłoby kod bardziej złożonym. Podczas pisania DAO pisarz nie powinien dbać o to, dokąd zmierza strumień: może to być plik, standardowe wyjście, odpowiedź HTTP, gniazdo, cokolwiek innego. Po uwzględnieniu tego problemu w logice serializacji staje się on znacznie bardziej złożony: musi znać określony typ strumienia i wywołać konstruktora.
Oprócz tej opcji nie jestem pewien, jaki byłby lepszy sposób modelowania tych obiektów, który jest prosty, elastyczny i pomaga zapobiegać błędom logicznym w kodzie, który go używa.
Przypadek użycia, z którym należy zintegrować rozwiązanie, to proste okno dialogowe wyboru pliku . Użytkownik wybiera „Otwórz ...” lub „Zapisz jako ...” z menu Plik, a program otwiera lub zapisuje WidgetDatabase. Dostępne będą również opcje „Importuj ...” i „Eksportuj ...” dla poszczególnych widżetów.
Gdy użytkownik wybierze plik do otwarcia lub zapisania, wxWidgets zwróci nazwę pliku. Program obsługi, który reaguje na to zdarzenie, musi być kodem ogólnego przeznaczenia, który pobiera nazwę pliku, pobiera serializator i wywołuje funkcję wykonującą duże podnoszenie. Najlepiej byłoby, gdyby ten projekt działał również, jeśli inny fragment kodu wykonuje operacje we / wy bez plików, takie jak wysyłanie WidgetDatabase do urządzenia mobilnego przez gniazdo.
Czy widżet zapisuje swój własny format? Czy współpracuje z istniejącymi formatami? Tak! Wszystkie powyższe. Wracając do okna plików, pomyśl o Microsoft Word. Microsoft miał swobodę opracowywania formatu DOCX, jednak chcieli z pewnymi ograniczeniami. Jednocześnie program Word odczytuje lub zapisuje starsze formaty i formaty innych firm (np. PDF). Ten program nie jest inny: format „binarny”, o którym mówię, to jeszcze nieokreślony wewnętrzny format przeznaczony do szybkości. Jednocześnie musi być w stanie czytać i pisać otwarte formaty standardowe w swojej domenie (bez znaczenia na pytanie), aby mógł współpracować z innym oprogramowaniem.
Wreszcie istnieje tylko jeden typ widżetu. Będą miały obiekty potomne, ale będą one obsługiwane przez tę logikę serializacji. Program nigdy nie ładuje zarówno widżetów, jak i zębatek. Taka konstrukcja tylko musi być związane z widgetów i WidgetDatabases.