I can think of an approach, based on a factory to serve up the objects based on a tag.
The difficulty here is not really how to decouple the deserialization of each object content, but rather to decouple the association of a tag and an object.
For example, let's say you have the following XML
<my_xml>
<bird> ... </bird>
</my_xml>
How do you know that you should build a Bird
object with the content of the <bird>
tag ?
There are 2 approaches there:
- 1 to 1 mapping, ig:
<my_xml>
represents a single object and thus knows how to deserialize itself.
- Collection:
<my_xml>
is nothing more than a loose collection of objects
The first is quite obvious, you know what to expect and can use a regular constructor.
The problem in C++ is that you have static typing, and that makes the second case more difficult, since you need virtual construction there.
Virtual construction can be achieved using prototypes though.
// Base class
class Serializable:
{
public:
virtual std::auto_ptr<XmlNode*> serialize() const = 0;
virtual std::auto_ptr<Serializable> deserialize(const XmlNode&) const = 0;
};
// Collection of prototypes
class Deserializer:
{
public:
static void Register(Tag tag, const Serializable* item)
{
GetMap()[tag] = item;
}
std::auto_ptr<Serializable> Create(const XmlNode& node)
{
return GetConstMap()[node.tag()]->deserialize(node);
// I wish I could write that ;)
}
private:
typedef std::map<Tag, const Serializable*> prototypes_t;
prototypes_t& GetMap()
{
static prototypes_t _Map;
return _Map;
}
prototypes_t const& GetConstMap() { return GetMap(); }
};
// Example
class Bird: public Serializable
{
virtual std::auto_ptr<Bird> deserialize(const XmlNode& node);
};
// In some cpp (bird.cpp is indicated)
const Bird myBirdPrototype;
Deserializer::Register('bird', myBirdPrototype);
Deserialization is always a bit messy in C++, dynamic typing really helps there :)
Note: it also works with streaming, but is a bit more complicated to put in place safely. The problem of streaming is that you ought to make sure not to read past your data and to read all of your data, so that the stream is in a 'good' state for the next object :)