Serializer Pattern
A serializer is an object that is responsible only for reading or writing objects. With QDataStream, it is already possible to serialize and deserialize all Qvariant -supported types, including QList, QMap, QVector, and others. For other file formats, or more complex object models, we isolate the reading/writing in separate reader and writer classes. These classes are examples of the Serializer pattern [Martin 98]. A serializer that reads and writes to files should handle all of the file and/or stream initialization and cleanup. A serializer could also be used to send objects over a network from one socket to another.[4] In that case it would be responsible for connecting and disconnecting to/from the appropriate socket. |
[4] Sockets are addressable entities, used as endpoints for sending and receiving data between computers. Qt has an abstract base class named QAbstractSocket, which provides the interface for working with various kinds of sockets, and a concrete QTcpSocket class, which provides a TCP (Transmission Control Protocol) socket.
In Figure 10.3, we have a UML diagram for two serializer classes. Together, they can read and write ContactList objects.
Figure 10.3. The Serializer pattern
In C++, serializer classes can have overloaded input/output operators, similar to iostream or QTextStream, so that we can use them with a familiar interface. To read a file into the container, it should be as easy as this:
ContactList cl; ContactListReader reader("somefile.txt"); reader >> cl;
And analogously, when it is time to output, the client code should look like this:
ContactListWriter writer("somefile.txt"); writer << cl;
We achieve this by defining the following insertion operators:
ContactListReader& operator>>(ContactListReader& reader, ContactList& cl); ContactListWriter& operator<< (ContactListWriter& writer, const ContactList& cl);
Example 10.9 shows how to implement customized i/o operators.
Example 10.9. src/containers/contact/serializer.cpp
[ . . . . ] ContactListReader& operator>>(ContactListReader& reader, ContactList& cl) { reader.read(cl); return reader; } ContactListWriter& operator<< (ContactListWriter& writer, const ContactList& cl) { writer.write(cl); return writer; } |
In Example 10.9, we defined global (non-member) operators. It would also be possible to write member function operators. The reason it is necessary to define non-member operators for iostream and QTextStream is because we cannot modify those classes to add member functions.
10.6.1. Benefits of the Serializer
By using the serializer, it will become much easier to change the file format, or the transport layer, with only small changes in client code. In addition, removing serialization code from the model makes the model simpler and easier to maintain.
Exercises: Serializer Pattern
1. |
Revisiting the Contact List exercise from Section 4.3, complete the implementation of ContactListWriter and ContactListReader classes for the ContactList, following the Serializer pattern, so that you can read and write a ContactList to a file. When each Contact is serialized, it should be written on a single line, in a tab-separated format (tabs separating each field in the record). Therefore, a serialized ContactList should have a sequence of lines, each line representing a Contact. |
2. |
Write a main program that supports, at minimum, the following command line arguments. Feel free to add others after these are working. Contact List Test Driver usage contact [-i inputfile] [-c] [-o outputfile] [-p] -i read contact list from specified inputfile into ContactList -c generate 10 more random contacts and add to ContactList -o write ContactList list to specified output file -p print ContactList to standard output If an invalid option is supplied, or if no options are supplied, it should print the above "Contact List Usage". |
Sorted Map Example
|