从XML加载OOP结构 - 如何设计代码

时间:2012-10-05 02:55:39

标签: xml oop

我有一个由许多类组成的结构,有点像:

  • 文档
    • 跟踪(每个文档可以有多个轨道)
      • 剪辑(每个曲目可以有多个剪辑)
    • (将来可能会添加其他类型)

我将这些文档存储为XML,如下所示: <文件><跟踪与GT;<剪辑GT; ...< /剪辑GT;< /轨道>< /文档与GT;

在每个班级中,我都有一个 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:一个“loader”类,它保留所有类型的加载方法,如:

// 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 个答案:

答案 0 :(得分: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采用相同的方法没有错。


第二种方法我会考虑引入反序列化器(或映射器或marshallers或任何你喜欢称之为的东西)作为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

这种方法的明显缺点是,现在您正在使用toXML在类中编写XML,但是使用反序列化器读取它,因此更改XML布局意味着更改为两个类。如果toXML被移入同一个类(可能会调用类<ModelClassName>XMLMapper),那么这个缺点就会消失。

这种方法的一个小缺点是,它使模型类和XML同步更复杂一些,因为每次更改都需要修改一对文件(模型类和反序列化器类)。仅仅为了从模型类中获取XML代码,这可能是值得的。

从这种方法中获得的解耦简化了模型类,并允许您在将来的输入和输出方面具有更大的灵活性,例如使用XML之外的其他东西来存储和传输对象。它还将特定于XML的代码封锁到它自己的文件集中。


我考虑的最低耦合,最复杂的方法类似于刚刚提到的解串器/映射器方法,但是以更具声明性的方式抽象出映射细节 - 类似于你的第二个做法。我已经在luabind和其他“C ++ to scripting language”映射中看到过这种方法。

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这样的脚本语言来管理第二种方法中的映射)并且我确信有一些我没有考虑过的方法,但是我希望这仍然能给你一些思考,并且你最终能够找到一个你觉得舒服的解决方案。