3

私はいくつかのクラスで構成される構造を持っています。

  • 書類
    • トラック (各ドキュメントには複数のトラックを含めることができます)
      • クリップ (各トラックには複数のクリップを含めることができます)
    • (他の種類は今後追加される可能性があります)

これらのドキュメントを次のように XML として保存しています: <Document><Track><Clip>...</Clip></Track></Document>

各クラスには、その内容を XML 形式で記述するtoXML()メソッドがあります。Document::toXML()は、その子に対してtoXML()を呼び出し、結果を結合します。このように、節約は非常に些細なことであり、私の意見では簡単に拡張できます。

しかし今、ローディング コードの設計方法に問題があります。

私が考えることができる2つの方法があります:

1: Document::fromXML()の巨大な if ステートメント、次のようなもの:

// Pseudo code
for each element {
   if element.name == "Track" createTrack(element)
   if element.name == "Clip" createClipOnLastTrack(element)
   // .. more statements for new types
}

2: 次のようなすべてのタイプのメソッドをロードし続ける「ローダー」クラス:

// Track.fromXML will be responsible for calling Clip.fromXML
Loader.register("Track", &Track.fromXML)
Loader.register("OtherType", &OtherType.fromXML)
// More register() calls for new types

for each element {
   // Call appropriate method based on tag name
   Loader.load(element.name, element)
}

#1 はあまり好きじゃない。ぎこちなく感じる。#2の方がいい感じですが、デザインが良いかどうかはわかりません。

それは...ですか?XML ドキュメントを実際のオブジェクト インスタンスのセットに変換する一般的/一般的な方法は他にありますか?

4

1 に答える 1

2

あなたの問題について私が知っていることに基づいて、最初のアプローチは合理的だと思います。2 番目のアプローチは、マッピングが常に単純であり、多くのマッピング変更を行うという確信がない限り、必要以上に複雑に見えます。

ただし、より完全な回答を得るために、私があなたの立場で検討する 3 つのアプローチについて説明します。各アプローチは結合と複雑さによって異なります。そのうちの 2 つについては基本的に説明しましたが、もう少し詳しく説明します。問題の全容がわからないため、それらのいずれも「信頼できる解決策」とは見なしませんが、言及する価値があると思います。


私が検討する最高の結合で最も複雑でないアプローチDocumentは、 、Track、およびの静的ファクトリ関数のセットですClip。これは、本質的にあなたが言及した最初のオプションです。このアプローチがあまり使用されているのを見たことがありませんが、他の開発者が使用している可能性があります私にはRuby/ActiveRecord感じがあります(これは判断ではなく、ただのランダムな考えです)。

//all examples are C++ish pseudo-code
Document* Document::fromXML(SomeXMLStream* stream) { 
    Document* doc = new Document();
    //read the details specific to Document, populate *doc

    //for each <Track> child in the stream...
    Track* track = Track::fromXML(stream);
    //add the track to *doc

    return doc;
}

Track* Track::fromXML(SomeXMLStream* stream) { 
    Track* track = new Track();
    //similar steps here

    //for each <Clip> child in the stream...
    Clip* clip = Clip::fromXML(stream);
    //and so on

    return track;
}

//similar code for Clip::fromXML(...)

結合度が高い (つまり、クラスが XML を認識している)とfromXML、ロジックをロジックのすぐ隣に配置できるという利点があります。toXMLこれは、ライターとリーダーを同じ場所で定義するのが合理的 (かつ便利) であるためです。XML レイアウトの変更には 2 つの変更が必要ですが (1 つはfromXMLで、もう 1 つは でtoXML)、変更は 1 つのファイルで行われます。

このアプローチのマイナス面はtoXML、クラス自体にコーディングする場合と同じマイナス面です。XML はハードコーディングされるため、そのままの XML を使用したほうがよいでしょう。toXMLしかし、実装に専念しているのであれば、 fromXML.


私が検討する2 番目のアプローチは、デシリアライザー (またはマッパー、マーシャラーなど、お好きな呼び方) を導入して、XML のアービターとして機能させることです。XML とモデルの結合は、 、 、および からこれらのデシリアライザーに移動DocumentTrackますClip。このアプローチは、手書きコードと自動生成コードの両方で「現場」で頻繁に使用されるのを見てきました。

Document* DocumentDeserializer::fromXML(SomeXMLStream* stream) {
    Document *doc = new Document();
    //read the details specific to Document, populate *doc

    //for each <Track> child in the stream...
    Track* track = TrackDeserializer::fromXML(stream);
    //add the track to *doc

    return doc;
}

//similar code for Track and Clip

このアプローチの明らかな欠点は、デシリアライザーを使用してクラスで XML を記述しているのにtoXML、デシリアライザーを使用してそれを読み取っていることです。したがって、XML レイアウトを変更すると、2 つのクラスが変更されることになります。が同じクラスに移動した場合toXML(おそらくクラスを呼び出します<ModelClassName>XMLMapper)、この欠点は解消されます。

このアプローチの小さな欠点の 1 つは、変更のたびにファイルのペア (モデル クラスとデシリアライザー クラス) を変更する必要があるため、モデル クラスと XML の同期が少し複雑になることです。これは、モデル クラスから XML コードを取得するためだけに価値がある場合があります。

このアプローチから得られるデカップリングにより、モデル クラスが簡素化され、たとえば XML 以外のものを使用してオブジェクトを格納および送信するなど、将来の入力と出力の柔軟性が向上します。また、XML 固有のコードを独自のファイル セットに封鎖します。


カップリングが最も低く、最も複雑なアプローチは、前述のデシリアライザー/マッパーのアプローチに似ていますが、マッピングの詳細がより宣言的な方法で抽象化されています-2番目のアプローチと同様です。luabindこのアプローチが、および他の「C++ からスクリプト言語へ」のマッピングで使用されているのを見てきました。

void DocumentDeserializer::configureDeserializer() { 
    //XMLMapping<T> is a templated mapping class that 
    //maps an element name to a field of T and deserializer function.
    XMLMapping<Document>::registerElementMapping("track", &Document::tracks, &TrackDeserializer::fromXML); 

    //Example of registering a new element that doesn't need a special deserializer.
    XMLMapping<Document>::registerElementMapping("name", &Document::name);

}

Document* DocumentDeserializer::fromXML(SomeXMLStream* stream) {
    Document *doc = new Document();

    //Allow the mapper to handle the details.
    XMLMapping<Document>::map(stream, doc);
    return doc;
 }

//similar code for Track and Clip

XML とモデル クラスの結合はコード内に残っていますが、1 つの場所 ( ) で宣言configureDeserializerされ、別の場所 ( ) で実行されfromXMLます。この分離により、マッピングのリストの最後に 1 行を追加するだけで済むため、後で新しい要素を追加する作業が簡素化されます。

欠点は、未知の量、つまりXMLMapping<T>クラスです。どの程度の複雑さを処理しなければならないのでしょうか? getter メソッドと setter メソッドを処理する必要がありますか、それともフィールドと直接やり取りする必要がありますか? 日付など、特殊な書式を持つ文字列値をどのように処理しますか? 1 つのフィールドに入力するために 2 つの要素を読み取る必要がある場合、または 2 つのフィールドに入力する 1 つの要素を読み取る必要がある場合はどうすればよいでしょうか? マッピング アプローチ便利ですが、それを機能させるだけでも長く骨の折れる時間がかかる可能性があります。また、最初の 2 つのアプローチでコード化するのが簡単な場合でも、このアプローチでマッピングに変換するのは非常に困難になる可能性があります。


以上が、私が検討する 3 つのアプローチです。これらに基づいて思いつく代替案はたくさんあります (たとえば、Lua のようなスクリプト言語を使用して 2 番目のアプローチでマッピングを管理する)。これがあなたに何か考えるきっかけを与え、最終的にあなたが納得できる解決策を見つけることができることを願っています.

于 2012-10-06T03:53:25.420 に答える