Before we get to the nuts and bolts of how to design an RWCollectable class, let's discuss a concrete example of why you might choose to design RWCollectable classes.
Suppose you run a bus company. To automate part of your ridership tracking system, you want to write classes that represent a bus, its set of customers, and its set of actual passengers. In order to be a passenger, a person must be a customer. Hence, the set of customers is a superset of the set of passengers. Also, a person can physically be on the bus only once, and there is no point in putting the same person on the customer list more than once. As the developer of this system, you must make sure there are no duplicates on either list.
These duplicates can be a problem. Suppose that the program needs to be able to save and restore information about the bus and its customers. When it comes time to polymorphically save the bus, if your program naïvely iterates over the set of customers, then over the set of passengers, saving each one, any person who is both a customer and a passenger is saved twice. When the program polymorphically restores the bus, the list of passengers will not simply refer to people already on the customer list. Instead, each passenger will have a separate instantiation on both lists.
You need some way of recognizing when a person has already been polymorphically saved to the stream and, instead of saving him or her again, merely saving a reference to the previous instance.
This is the job of class RWCollectable. Objects that inherit from RWCollectable have the ability to save not only their contents, but also their relationships with other objects that inherit from RWCollectable. We call this feature isomorphic persistence. Class RWCollectable has isomorphic persistence, but more than that, it can determine at run time the type of the object to be saved or restored. We call the type of persistence provided by RWCollectable polymorphic persistence, and recognize it as a superset of isomorphic persistence.
The code below shows how we might declare the classes described in the previous section. Later we'll use the macro RWDECLARE_COLLECTABLE and discuss our function choices. You'll find the complete code from which this example is taken at the end of this chapter; it is also given as the bus example in the toolexam directory.
class Bus : public RWCollectable { RWDECLARE_COLLECTABLE(Bus) public: Bus(); Bus(int busno, const RWCString& driver); ~Bus(); // Inherited from class "RWCollectable": Rwspace binaryStoreSize() const; int compareTo(const RWCollectable*) const; RWBoolean isEqual(const RWCollectable*) const; unsigned hash() const; void restoreGuts(RWFile&); void restoreGuts(RWvistream&); void saveGuts(RWFile&) const; void saveGuts(RWvostream&) const; void addPassenger(const char* name); void addCustomer(const char* name); size_t customers() const; size_t passengers() const; RWCString driver() const {return driver_;} int number() const {return busNumber_;} private: RWSet customers_; RWSet* passengers_; int busNumber_; RWCString driver_; }; class Client : public RWCollectable { RWDECLARE_COLLECTABLE(Client) Client(const char* name) : name_(name) {} private: RWCString name_; //ignore other client information for this example };
Note how both classes inherit from RWCollectable. We have chosen to implement the set of customers by using class RWSet, which does not allow duplicate entries. This will guarantee that the same person is not entered into the customer list more than once. For the same reason, we have also chosen to implement the set of passengers using class RWSet. However, we have chosen to have this set live on the heap. This will help illustrate some points in the coming discussion.