从XML加载面向对象的结构——代码如何设计

3

我有一个由多个类组成的结构,就像这样:

  • Document
    • Track(每个文档可以有多个轨道)
      • Clip(每个轨道可以有多个剪辑)
    • (其他类型可能会在将来添加)

我将这些文档存储为XML,就像这样: <Document><Track><Clip>...</Clip></Track></Document>

在每个类中,我都有一个toXML()方法,以XML形式描述其内容。 Document :: toXML()负责调用其子对象上的toXML()并组合结果。 因此,在我看来,保存非常简单且易于扩展。

但现在我在设计加载代码时遇到了麻烦。

我能想到两种方法:

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文档转换为实际对象实例的集合?
1个回答

2
根据我所知,我认为第一种方法是合理的选择。除非确定映射一直保持简单且需要频繁更改映射,否则第二种方法似乎过于复杂。
不过,作为一个更全面的答案,我会提出三种方法供您参考,每种方法的耦合度和复杂性都有所不同。其中两种方法已经被您提到,但我会详细展开。基于我不了解您问题的全部范围,我不认为它们是"权威的解决方案",但我认为值得一提。
我认为最高耦合度、最少复杂度的方法是在DocumentTrackClip中创建一组静态工厂函数,这基本上就是您提到的第一种选择。没有看到过这种方法的广泛使用,但很可能其他开发人员有使用过。对我来说,它有一种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布局的更改需要进行两个更改(一个在fromXML中,另一个在toXML中),但更改发生在一个文件中。
这种方法的缺点与在类本身中编写toXML代码的缺点相同:您最好喜欢现有的XML,因为它将被硬编码。但是,如果您致力于您的toXML实现,我认为致力于使用fromXML相同的方法没有问题。
第二种方法会引入反序列化程序(或映射器或编组程序或您喜欢的任何名称),作为XML的仲裁者。 XML和模型之间的耦合移出了DocumentTrackClip,并转移到这些反序列化程序中。我曾经看到过这种方法在手写代码和自动生成的代码中经常被使用。
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代码,用反序列化器读取它,因此XML布局的更改意味着必须更改两个类。如果将toXML移入同一类(也许称这些类为<ModelClassName>XMLMapper),则可以消除这种缺点。
这种方法的一个小缺点是,它使得模型类和XML文件的同步变得更加复杂,因为每次更改都需要修改一对文件(模型类和反序列化器类)。但这可能值得付出,只是为了将XML代码从模型类中分离出来。
这种方法获得的解耦简化了模型类,并允许您在未来的输入和输出方面拥有更多的灵活性,例如使用其他方式存储和传输对象。它还将XML特定的代码隔离到其自己的一组文件中。
我考虑的最低耦合、最复杂的方法与刚才提到的反序列化器/映射器方法类似,但是将映射细节以更声明性的方式抽象出来——类似于您的第二种方法。我见过这种方法在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和模型类之间的耦合仍然存在于代码中,但现在它在一个地方(configureDeserializer)声明,在另一个地方执行(fromXML)。这种分离简化了以后添加新元素的操作,因为现在只需要在映射列表的末尾添加一行即可。
缺点是未知数量的XMLMapping<T>类:它必须处理多少复杂性?它应该处理getter和setter方法还是直接访问字段?它如何处理具有特殊格式(如日期)的字符串值?如果需要读取两个元素来填充一个字段或者一个元素填充两个字段怎么办?虽然映射方法非常方便,但要使其正常工作可能需要很长时间,而前两种方法中容易编码的情况在这种方法中可能会非常难以转换为映射。
所以这些就是我考虑的三种方法。基于这些方法,您可以提出许多其他替代方案(例如,在第二种方法中使用像Lua这样的脚本语言来管理映射),我相信还有我没有考虑过的方法,但我希望这些内容能给您一些启示,并且最终能够找到一种您满意的解决方案。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接