我有一个由许多类组成的结构,有点像这样:
文件
跟踪(每个文档可以有多个跟踪)
剪辑(每个曲目可以有多个剪辑)
(以后可能会增加其他类型)
我将这些文档存储为XML,如下所示:
在每个类中,我都有一个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)
}
我真的不喜欢,感觉很笨拙。#感觉好多了,但我不确定这是不是一个好的设计。
它是?是否有其他常见/流行的方法将xml文档转换为一组实际的对象实例?
最佳答案
根据我对你问题的了解,我认为第一种方法是合理的。第二种方法似乎比必要的更复杂,除非您确信映射将始终保持简单,并且您将进行许多映射更改。
不过,为了给出一个更彻底的答案,我将介绍三种方法,我将考虑在你的立场。每种方法都因耦合和复杂性而异,其中两种基本上已经介绍过,但我将进一步介绍。我不认为它们是“权威的解决方案”,因为我不知道你的问题的全部范围,但我认为它们值得一提。
我认为耦合度最高、最不复杂的方法是Document
、Track
和Clip
中的一组静态工厂函数,这实际上是您提到的第一个选项。我没见过这种方法有多大用处,但很可能其他开发人员也有。对我来说,它有一种红宝石般的感觉(这不是一种判断,只是一种随机的想法)。
//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的类)为您提供了将
ActiveRecord
逻辑放在fromXML
逻辑旁边的优势,因为将writer和reader定义在同一位置是合理且方便的。XML布局的更改需要两个更改(一个在toXML
中,另一个在fromXML
中),但更改发生在一个文件中。这种方法的缺点与在类本身中编写
toXML
的缺点相同:您最好喜欢xml,因为它将是硬编码的。但是,如果您致力于toXML
实现,我认为使用toXML
实现相同的方法没有错。我将考虑的第二种方法是引入反序列化程序(或映射程序或封送处理程序,或者您更愿意调用它们的任何程序)来充当xml的仲裁程序。XML和模型之间的耦合从
fromXML
、Document
和Track
移到这些反序列化程序中。我见过这种方法经常在“现场”使用,包括手写和自动生成的代码。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
这种方法的明显缺点是,现在使用
Clip
在类中编写xml,但使用反序列化程序读取它,因此对xml布局的更改意味着对两个类的更改。如果toXML
被移到同一个类中(可能称为类toXML
),这个缺点就消失了。这种方法的一个小缺点是,它使同步模型类和xml变得更加复杂,因为每次更改都需要修改一对文件(模型类和反序列化程序类)。仅从模型类中获取xml代码可能是值得的。
从这种方法中获得的分离简化了模型类,并允许您在将来的输入和输出方面有更大的灵活性,例如使用XML以外的其他东西来存储和传输对象。它还将特定于xml的代码划分为自己的一组文件。
我认为耦合度最低、最复杂的方法类似于刚才提到的反序列化器/映射器方法,但是映射细节是以更声明的方式抽象出来的——类似于第二种方法。我已经看到这种方法在
<ModelClassName>XMLMapper
和其他“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和模型类之间的耦合仍然存在于代码中,但现在它在一个地方声明(
luabind
)并在另一个地方执行(configureDeserializer
)。这种分离简化了以后添加新元素的过程,因为现在只需要在映射列表的末尾添加一行。缺点是未知的数量,
fromXML
类:它必须处理多少复杂性?它应该处理getter和setter方法还是直接与字段对话?它将如何处理具有特殊格式(如日期)的字符串值?如果需要读取两个元素来填充一个字段,或者需要读取一个元素来填充两个字段,该怎么办?尽管映射方法可能很方便,但要使其工作起来可能需要很长的时间,而且前两种方法中易于编码的情况在这种方法中很难转化为映射。这就是我要考虑的三种方法。基于这些,您可以提出很多替代方案(例如,在第二种方法中使用lua这样的脚本语言来管理映射),我相信有一些方法是我没有考虑过的,但是我希望这仍然能给您一些思考的余地,并且你最终能找到一个你满意的解决方案。
关于xml - 从XML加载OOP结构-如何设计代码,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/12738864/