11.5. Hiding Unnecessary Information from the class User
That rule applies only to functions that are accessible to the user of the polymorphic object. The GetType function is intended for use only in the implementation of the polymorphic object, not by its users; therefore, it is not only possible but desirable to keep it "hidden" by declaring it inside one of the worker classes. Because the user never creates an object of any of the worker classes directly, declaring a function in one of those classes has much the same effect as making it a private member function. As we have already seen, hiding as many implementation details as possible helps to improve the robustness of our programs.
I should also mention the different data types for member variables in the HomeItem classes that have similar functions to those in the StockItem classes. In HomeItemBasic, we are using a long to hold a date, where we used a string in the DatedStockItem class. A sufficient reason for this change is that in the current class, we are getting the date from the user, so we don't have the problem of converting the system date to a storable value as we did with the implementation of the former class. As for the double we're using to store the price information, that's a more sensible data type than short for numbers that may have decimal parts. I avoided using it in the earlier example only to simplify the presentation, but at this point I don't think it should cause you any trouble.
Aside from these details, this polymorphic object's definition is very similar to the one for the StockItem polymorphic object. The similarity between the interfaces (and corresponding similarity of implementations) of polymorphic objects is good news because it makes generating a new polymorphic object interface and basic implementation quite easy. It took me only a couple of hours to write the initial version of the HomeItem classes using StockItem as a starting point. What is even more amazing is that the test program (Figure 11.3) worked correctly the very first time I ran it!1
The Initial HomeItem Test Program
I don't think that program needs much explanation. It is exactly the same as the corresponding StockItem test program in Figure 10.21 on page 696, with the obvious exception of the types of the objects and the name of the ifstream used to read the data. Figure 11.4 shows the result of running the above program.
Now that we've gone over the interfaces for the classes that cooperate to make a polymorphic HomeItem object, as well as the first test program and its output, we can see the initial implementation in Figure 11.5.
What does this first version of HomeItem do for us? Not too much; it merely allows us to read HomeItem objects from a file, display them, and write them out to a file. Although we've seen the implementation of similar functions in the StockItem class, it should still be worthwhile to discuss how these are similar to and different from the corresponding functions in the HomeItem classes. However, to avoid too much repetition we'll skip the functions that are essentially identical in these two cases, including the following functions for the base class, HomeItem:
- 1. operator <<;
- 2. the copy constructor;
- 3. the default constructor;
- 4. operator =;
- 5. the destructor;
- 6. the normal constructors that create worker objects with known initial data;
- 7. the "special" constructor that prevents an infinite regress when creating a worker object.
- 1. the default constructor;
- 2. the copy constructor;
- 3. operator =;
- 4. the normal constructor;
- 5. the destructor.
Calling this function is an error and will cause the program to exit. In case you're wondering why this is an error, you may be happy to know that Susan had the same question.
- Susan: Why is calling HomeItem::Write an error?
- Steve: Because this function exists solely for use by operator << in writing out the data for a derived class object. Therefore, if it is ever called for a HomeItem base class object, we know that someone has used the function incorrectly, and we leave the program before any more incorrect processing can occur.
This is quite similar in outline to the corresponding function in StockItem in that it reads data from an input source and creates a worker object of the correct type based on the value of one of the fields. However, this function does have a few noticeable differences from the StockItem version:
- 1. This function skips any empty lines that may be in the input file before reading the type of the object.
- 2. The type is specified explicitly as an extra field ("Basic" or "Music" in the current cases), rather than by the use of a special "0" value for the expiration date field, as in the DatedStockItem input file.
- 3. This function calls istream::fail() to determine whether the program has attempted to read more information from the input file than it contains (or if any other error has occurred when trying to read from the input file). If such an error occurs, the operator >> function assigns a default-constructed HomeItem to the reference argument Item and returns to the calling function immediately.
- 4. The local variables Artist, TrackCount, and Track are created only when the function needs them (for a "Music" object) rather than at the beginning of the function as has been our practice until now.
- 5. In the case of a "Music" object, one of those local variables is a Vec of strings.
- 6. The index variable i is also created only when it is needed, at the beginning of the