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