C ++アプリケーションを書いています。ほとんどのアプリケーションはデータ引用を読み書きする必要があり、これも例外ではありません。データモデルとシリアル化ロジックの高レベルデザインを作成しました。この質問は、これらの特定の目標を念頭に置いて、私のデザインのレビューを要求しています:
任意の形式(rawバイナリ、XML、JSONなど)でデータモデルを読み書きする簡単で柔軟な方法を提供する。al。データの形式は、シリアル化を要求しているコードと同様に、データ自体から分離する必要があります。
シリアル化が合理的に可能な限りエラーが発生しないようにするため。I / Oは、さまざまな理由で本質的にリスクが高くなります。私の設計では、失敗する方法が増えているのですか?もしそうなら、それらのリスクを軽減するために設計をどのようにリファクタリングできますか?
このプロジェクトは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;
};
最後に、目的のI / Oタイプに適切なリーダー/ライターを取得するコードを次に示します。リーダー/ライターのサブクラスも定義されますが、これらはデザインレビューに何も追加しません。
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 */);
}
};
私の設計の指定された目標に従って、私は1つの特定の懸念があります。C ++ストリームはテキストモードまたはバイナリモードで開くことができますが、すでに開いているストリームを確認する方法はありません。プログラマーのエラーにより、たとえばXMLまたはJSONリーダー/ライターにバイナリストリームを提供することは可能です。これにより、微妙な(またはそれほど微妙ではない)エラーが発生する可能性があります。私はコードが速く失敗することを望みますが、このデザインがそれを実行するかどうかはわかりません。
これを回避する1つの方法は、ストリームを開く責任をリーダーまたはライターに任せることですが、これはSRPに違反し、コードがより複雑になると考えています。DAOを作成するとき、ライターはストリームの行き先を気にする必要はありません。ファイル、標準出力、HTTP応答、ソケットなど、何でもかまいません。いったんその懸念がシリアライゼーションロジックにカプセル化されると、それははるかに複雑になります。特定のタイプのストリームと呼び出すコンストラクターを知る必要があります。
そのオプションを除けば、これらのオブジェクトをモデル化するための、シンプルで柔軟性があり、それを使用するコードの論理エラーを防ぐのに役立つ方法は何なのかわかりません。
ソリューションを統合する必要がある使用例は、単純なファイル選択ダイアログボックスです。ユーザーが[ファイル]メニューから[開く...]または[名前を付けて保存...]を選択すると、プログラムはWidgetDatabaseを開くか保存します。個々のウィジェットには「インポート...」と「エクスポート...」オプションもあります。
ユーザーが開くまたは保存するファイルを選択すると、wxWidgetsはファイル名を返します。そのイベントに応答するハンドラーは、ファイル名を取り、シリアライザーを取得し、重い処理を行う関数を呼び出す汎用コードである必要があります。理想的には、ソケットを介してWidgetDatabaseをモバイルデバイスに送信するなど、別のコードがファイル以外のI / Oを実行している場合にも、この設計は機能します。
ウィジェットは独自の形式で保存しますか?既存のフォーマットと相互運用できますか?はい!上記のすべて。ファイルダイアログに戻って、Microsoft Wordについて考えてみましょう。マイクロソフトはDOCX形式を自由に開発できましたが、特定の制約の範囲内で必要でした。同時に、Wordはレガシーおよびサードパーティのフォーマット(PDFなど)の読み取りまたは書き込みも行います。このプログラムも例外ではありません。私が話す「バイナリ」形式は、速度を上げるために設計された、まだ定義されていない内部形式です。同時に、他のソフトウェアと連携できるように、ドメイン内のオープンスタンダードフォーマットを読み書きできるようにする必要があります(質問とは関係ありません)。
最後に、ウィジェットのタイプは1つだけです。子オブジェクトがありますが、これらはこのシリアル化ロジックによって処理されます。プログラムはウィジェットとスプロケットの両方を決してロードしません。この設計はWidgetsとWidgetDatabases のみを考慮する必要があります。