我有一个由许多类组成的结构,有点像:
我将这些文档存储为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文档转换为一组实际的对象实例?答案 0 :(得分: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布局的更改需要进行两项更改(fromXML
中的一项更改,toXML
中的另一项更改),但更改将在一个文件中进行。
这种方法的缺点与类本身编码toXML
的缺点相同:你最好喜欢XML,因为它是硬编码的。但是,如果您致力于toXML
实施,我认为使用fromXML
采用相同的方法没有错。
第二种方法我会考虑引入反序列化器(或映射器或marshallers或任何你喜欢称之为的东西)作为XML的仲裁器。 XML和模型之间的耦合从Document
,Track
和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
这种方法的明显缺点是,现在您正在使用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这样的脚本语言来管理第二种方法中的映射)并且我确信有一些我没有考虑过的方法,但是我希望这仍然能给你一些思考,并且你最终能够找到一个你觉得舒服的解决方案。