根据我对您问题的了解,我认为第一种方法是合理的。第二种方法似乎比必要的更复杂,除非您确信映射将始终保持简单并且您将进行大量映射更改。
不过,为了给出更全面的答案,我将介绍三种我会在您的职位上考虑的方法。每种方法都因耦合性和复杂性而异,其中两种您已经基本介绍过,但我将进一步充实。我不认为它们中的任何一个是“权威解决方案”,因为我不知道您的问题的全部范围,但我认为它们值得一提。
我会考虑的最高耦合、最简单的方法是、 和中的一组静态工厂函数Document
,这基本上是您提到的第一个选项。我还没有看到这种方法被大量使用,但其他开发人员可能已经使用过。它对我有一种 Ruby/感觉(这不是判断,只是一个随机的想法)。Track
Clip
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 和模型之间的耦合从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
这种方法的明显缺点是,现在您在类中编写 XML,toXML
但使用反序列化器读取它,因此对 XML 布局的更改意味着对两个类的更改。如果toXML
被移动到同一个类(可能称为 classes <ModelClassName>XMLMapper
),这个缺点就消失了。
这种方法的一个小缺点是它使模型类和 XML 的同步变得更加复杂,因为每次更改都需要修改一对文件(模型类和反序列化器类)。仅仅从模型类中获取 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 这样的脚本语言来管理第二种方法中的映射),我敢肯定有一些我没有考虑过的方法,但是我希望这仍然可以让您思考一些问题,并且您最终能够找到自己满意的解决方案。