web-dev-qa-db-ja.com

可変XMLを処理するためのより保守しやすい方法

アプリケーションのデータはXMLファイルから取得されます。 XMLファイルはバージョン管理されていますが、新しいXMLバージョンファイルが頻繁にあります。各XMLファイルの構造は、他のバージョンに関して変更されます。わずかな変化もあれば、大きな変化もあります。

たとえば、バージョンXMLFileV1に次のようなものが含まれているとします。

<Shape>
  <Id>0</Id>
  <Features>
    <Feature>
      <Name>Name0</Name>
      <Color>Color0</Color>
    </Feature>
    <Feature>
      <Name>Name00</Name>
      <Color>Color00</Color>
    </Feature>
  </Features>
</Shape>

同じ "Shape"要素のFileXMLV2の内容は次のようになります。

<Shape>
  <SubShapes>
    <Subshape>
      <Id>0</Id>
      <Code>00</Code>
    </Subshape>
    <Subshape>
      <Id>1</Id>
      <Code>01</Code>
    </Subshape>
  <SubShapes>
  <Features>
    <Feature>
      <Name>Name0</Name>
      <Color>Color0</Color>
    </Feature>
    <Feature>
      <Name>Name00</Name>
      <Color>Color00</Color>
    </Feature>
  </Features>
</Shape>

アプリケーションは、現在のバージョンだけでなく、あらゆるバージョンのXMLファイルを読み取って処理できる必要があります。

上記の状況では、安定した"Shape" XMLファイルのすべてのバージョンでシリアライズ/デシリアライズできるクラス。

私はそのような状況に対処するために次のオプションを考えました:

  1. XMLファイルの最も古いバージョンに対応するShapeの基本クラスを作成します。次に、XMLFileV1としましょうShapeV1Shapeから派生し、必要な変更をこれに追加しますShapeV1 XMLFileV1によると。したがって、XMLfileV2が到着し、要素が再度変更されたら、新しいShapeV2ShapeV1(またはベースからShape必要に応じて)新しいXMLFileV2要件に準拠します。 XMLファイルは対応する「Shape」オブジェクトと正確に「マッピング」されるため、シリアライゼーション/デシリアライゼーションが可能になる可能性があります。
  2. XMLファイルの現在のバージョンの形式でShapeクラスを1つだけ作成します。したがって、アプリケーションが古いバージョンのXMLファイルを読み取る場合、古いXMLファイルに存在しないShapeクラスのフィールドは空白のままになります。現在のバージョンよりも新しいバージョンが到着したら、Shapeクラスを変更して必要なフィールドを追加します。オプション1のようにShapeクラスをシリアル化/逆シリアル化することはできませんが、XMLの「行ごと」のスタイルを読み取ります(C#でXMLReaderを使用するなど)。

だから私の質問は:

  • 上記のアプローチのどれが長期的に維持する方が良いですか?
  • 私が提示した両方よりも優れたアプローチがありますか?

ありがとう!

3
trofinao

さまざまなバージョン(アプローチ1)のクラスを作成しても、適切にスケーリングされません。来る多くのバージョンがある場合、あなたは決して良いことではないクラスのインフレに終わります。次に、V2のサブクラスとしてモデル化できないShapeV3があり、V1または基本クラスのサブクラスだけであるにもかかわらず、V2と同様の機能を提供する場合はどうなりますか?次に、継承なしでこれらのすべてのクラスを作成し、共通のインターフェイスさえも共有しないクラスの束になるだけです。

ですから、私はアプローチ2が好きですが、実装方法がよくわかりません。私はShapeクラスを作成します。これは、任意のShapeが保存する必要のあるすべてのものを保存し、異なるバージョンのインポーターとエクスポーターを記述することができます。これらは、独自のクラスである必要はありません。それらは、関数または静的メソッドでもかまいません。単一のクラス。

類推するには:処理する必要がある画像ファイルを送信していると考えてください。それらはPNG、JPEG、またはBMPである可能性がありますが、明日はWebPになる可能性もあります。つまり、画像データを格納するImageという名前のクラスを作成し、個々のファイル形式を読み取ってImageオブジェクトを出力できるインポーターと、Imageオブジェクトを特定のファイル形式にエクスポートできるエクスポーターを作成します。

1
Mecki

いくつかの選択肢があります。まず、オブジェクトは、データが変更されたからといって変更すべきではありません。これは、基本的な概念であるオブジェクト指向の単なる基本的な帰結です。その1つは、オブジェクトが動作によって定義され、データがその動作でカプセル化されることです。

  1. Shapeを定義して、キャンバスへの描画、面積の計算など、必要な動作を実行できます。この方法では、ワイヤー形式が変更されたときに変更する必要はありません。パーサーを拡張するか、新しいパーサーメソッドを提供するだけです。

  2. Shapeをインターフェイスにすることができます。データではなく動作のみを定義します。そして、さまざまなワイヤ形式に基づいてさまざまな実装があります。これは、さまざまな形式のシェイプの動作も少し異なる場合に適しています。

dataを伝播せず、適切な動作で非表示にすると、システムの保守性が大幅に向上します。副作用として、それはさらにオブジェクト指向になります。

1

それはあなたのシェイプが何のために使われるかによります!

短い答え

RobertBräutigamの答え かなりそれを釘付けにします。これは技術的にはあなたができる最高のことです。 Shapeクラスは、それほど多くを公開する必要はありませんすべてのメンバーを公開する必要はありません。つまり、あなたがsingの場合、Shapeクラスです。私たちが知っている限りでは、表現の目的でShapeを使用している可能性があるため、これらのすべてのプロパティをどこかで何らかの形で表示する必要があります。この問題に関しては、X-> Yの問題を扱っているため、詳細が必要になります。

(Too?)長い答え

アプリケーションを、国際企業の営業部門と考えてください。従業員はShapesです(わかっています、わかっています。世界中の人々が売上を管理する必要があります。 残念ながら、共同作業を行うすべてのクライアントは、自分の場所でのやり取りと、自分の言語でのコミュニケーションを必要とします(申し訳ありませんが、あなたの要件は、類推のこのような厳しいルールに対応しています)!次に、指定したオプションは次のシナリオを表します。

  • オプション1(ベースShapeクラスの異なるクラスバージョン)。日本で大事なセールが登場。あなたの営業部門では、誰も日本語を話しません。あなたは日本語を話す誰か(そして熟練した営業担当者)を見つけて(そしてそうする必要があります)そして彼らをその仕事に送る必要があります!つまり、オプション1は、販売先の国の言語を話す人を送る営業部門に対応します。これは、対応する言語と同じくらい多くの人々が必要であることを意味します。従業員が3つの言語を流暢に話せば、それほど重要ではありません...彼らは同時に3つの場所にいることはできません(ha、gotcha!)。

  • オプション2(時間内に再適応されるShapeクラスは1つのみ)。フランスでは重要な販売が行われます。あなたはそこに行く必要があり、あなたは今までに、あなたがたまたま必要とする言語に流暢なトップの営業担当者が実際に数十ドルも来ないことを理解しました。しかし、営業部門素晴らしい営業担当者はいますか。ですから、フランス語の集中コースを1か月間アレンジし、基本を理解するためだけに顧客が理解できるようにします。オランダ生まれの優れた営業担当者がフランス語を話せるようになりました。来月、彼らはドイツ語も話す必要があります(ああ、彼らはすでに多くのドイツ語を話します。OK、それをフィンランド語にしてください)。

私がどこに到達しているのか理解できれば、どちらのオプションも最適なリソース管理を表していないです。オプションが無視している中心的な問題は、アプリケーションは、自分以外のコンピューターで存続する必要があるです。ソースコードの更新時々は、コードを再度コンパイルするコードを再度デプロイするである必要があることを意味します...ファイル形式が変更されるたびに、アプリケーション全体の新しいバージョン(OK、おそらく一部のモジュールのみ)を出荷するにはまあ、あなたはかもしれないですが、これはすぐに問題になる可能性があり、リソースを管理するための最良の方法ではありません。

入る

  • オプション:辞書や翻訳者を使用してください!はい、あなたが一時的にまたは恒久的に行く国から誰かを雇うと、彼らはコミュニケーションを仲介し、従業員に新しい言語を学ぶよりもはるかに少ない時間で多くの成功を収めることができます。時々(これは、まあ、率直に言って、私が上にそれを上に見せたように思ったほど簡単ではありません)。

したがって、アプリケーションは遠い土地でも存続する必要があるため、追加の「ヘルパー」辞書を使用することをお勧めします。 「命令」XML(一種のスキーマのような)マップ。この付随するXMLには、何が解析され、最終的に何に割り当てられるかに関する情報が含まれます。 c#.NETで次の(単純な)例を確認してください。

あなたのクラス:

class PropertyObject
{
   //Stuff that MAY become necessary in the future.
    Dictionary<string, string> m_Properties;
    Dictionary<string, Type> m_PropertyTypes;

    List<PropertyObject> m_NestedObjects;
}

class Shape : PropertyObject
{
    //Necessary stuff that you KNOW should exist!
    List<Feature> m_Features;
    string m_Id;
    //etc..
}

class Feature : PropertyObject
{
    string m_Name;
    Color m_Color;
}

あなたのXML:

<Shape>
  <Id>0</Id>
  <Features>
    <Feature>
      <Name>Name0</Name>
      <Color>Color0</Color>
    </Feature>
    <Feature>
      <Name>Name00</Name>
      <Color>Color00</Color>
    </Feature>
  </Features>
</Shape>

あなたの潜在的な地図:

<xs:Shape>
    <member id="Id" mapsTo="m_id" type="string"/>
    <child id="Features" mapsTo="m_Features" type="Feature"/>
</xs:Shape>
<xs:Feature>
    <member id="Name" mapsTo="m_name" type="string"/>
    <!-- You might like to include full namespaces in some cases, for example... -->
    <member id="Color" mapsTo="m_Color" type="System.Drawing.Color"/>
</xs:Feature>

これで、発生する可能性のあるすべての変更で、アセンブリ/アプリケーション全体ではなく、マップのみを変更するが必要になります。次の例について考えてみましょう。当面は役に立たないプロパティが表示されます。

新しいXML:

<Shape>
  <Id>0</Id>
  <Gender>Female</Gender>
  <Features>
    <Feature>
      <Name>Name0</Name>
      <Color>Color0</Color>
    </Feature>
    <Feature>
      <Name>Name00</Name>
      <Color>Color00</Color>
    </Feature>
  </Features>
</Shape>

新しいマップ:

<xs:Shape>
    <member id="Id" mapsTo="m_id" type="string"/>
    <child id="Features" mapsTo="m_Features" type="Feature"/>
    <general id="Gender" type="string"/>
</xs:Shape>
<xs:Feature>
    <member id="Name" mapsTo="m_name" type="string"/>
    <!-- You might like to include full namespaces in some cases, for example... -->
    <member id="Color" mapsTo="m_Color" type="System.Drawing.Color"/>
</xs:Feature>

そして、generalとしてマップされたフィールドを解析するために適切な注意を払っています。たとえば、m_Propertiesディクショナリを単純な文字列として、そのタイプ(存在する場合は、文字列と見なすことができます)。他の誰かが<ignore id="Gender"/>こうすることで、skip無関係なものだけを使用できます。もちろん、新しい型が出現した場合、haveで再コンパイルできますが、その方向でこの問題を軽減するためのステップが存在する場合があります。 XML全体をオブジェクト内に格納して、動的に解析することもできます。

私はおそらくこれを過度に単純化(または過剰に設計)していますが、一般的にはconceptualの方向性を提供しようとしています。すでに利用可能な可能性のあるツールを調べて、単独で構成可能である外部マッピングを使用してみます。要件が変更された場合非常に頻繁に適応するために常にShape- shiftingするのではなく、単に 抽象化の追加レイヤーを導入 します。

0
Vector Zita

dynamicsの使用を検討するでしょう。ハイブリッドタイプを実行することもできます。バージョン1.0、または変更されない基本バージョンのプロパティは通常どおりにコード化され、変更は動的に処理されます。 ExpandoObjectを使用した実装。

これは将来に備えて、単一のプロパティ、リスト、複雑なネストされたプロパティの両方で、新しいプロパティをランタイムに追加できるようにする必要があります。取引を本当に甘くするために、あなたは得ます:

  1. 静的プロパティと動的プロパティをデータバインドすると、通常の型として表示されます(ただし、動的プロパティでIntelliSenseを取得することはできません)。
  2. INotifyPopertyChangedは1行の実装で無料で投入されます
  3. メソッドとイベントランタイムを追加することもできます

疑似コードの例、おそらくそれを機能させるためにいくつかのビットを変更する必要があります:

public class DynamicXmlDataObject
{
    public static DynamicXmlDataObject Create(XElement xmlData)
    {
        var newDataObject = new DynamicXmlDataObject(xmlData);
        newDataObject.ParseStaticData(null, xmlData);
        newDataObject.ParseDynamicData(null ,xmlData);
        return newDataObject;
    }

    private DynamicXmlDataObject(XElement xmlData)
    {
        TimeCreated = DateTime.Now;
        Name = xmlData.Name.LocalName;
        XmlElements = xmlData.DescendantsAndSelf();
        DynamicProperties = new ExpandoObject();
    }

    public DateTime TimeCreated { get; }
    public string Name { get; }
    public string Version { get; }

    public string Whatever { get; private set; }
    public string AndSoOn { get; private set; }

    public IEnumerable<XElement> XmlElements { get; }
    private ExpandoObject DynamicProperties { get; }

    private void ParseStaticData(dynamic parent, XElement xmlData)
    {
        //Do stuff like...
        Whatever = xmlData.DescendantsAndSelf(nameof(Whatever)).FirstOrDefault().Value;
        AndSoOn = xmlData.DescendantsAndSelf(nameof(AndSoOn)).FirstOrDefault().Value;
    }

    private void ParseDynamicData(dynamic parent, XElement xmlData)
    {
        //Do something like...
        if (xmlData.HasElements)
        {
            if (ElementIsCollection(xmlData))
            {
                var item = new ExpandoObject();
                var list = new List<dynamic>();
                foreach (var element in xmlData.Elements())
                {
                    ParseDynamicData(list, element);
                }

                AddProperty(item, xmlData.Elements().First().Name.LocalName, list);
                AddProperty(parent, xmlData.Name.ToString(), item);
            }
            else
            {
                var item = new ExpandoObject();
                foreach (var attribute in node.Attributes())
                {
                    AddProperty(item, attribute.Name.ToString(), attribute.Value.Trim());
                }

                foreach (var element in xmlData.Elements())
                {
                    ParseDynamicData(item, element);
                }
                AddProperty(parent, xmlData.Name.ToString(), item);
            }
        }
        else
        {
            AddProperty(parent, xmlData.Name.ToString(), xmlData.Value.Trim());
        }
    }

    private static void AddProperty(dynamic parent, string name, object value)
    {
        if (parent is List<dynamic>)
        {
            (parent as List<dynamic>).Add(value);
        }
        else
        {
            (parent as IDictionary<string, object>)[name] = value;
        }
    }

    private bool ElementIsCollection(XElement element) => element.Elements(element.Elements().First().Name.LocalName).Count() > 1;
}
0
Carsten