Adding Expiration Dates

Now we want to add one wrinkle to this algorithm: handling items that have expiration dates. This actually applies to a fair number of items in a typical grocery store, including dairy products, meats, and even dry cereals. To keep things as simple as possible, we'll assume that whenever we buy a batch of some item with an expiration date, all of the items of that type have the same date. When we get to the expiration date of a given StockItem, we send back all of the items and reorder as though we had no items in stock.

The first question is how we store the expiration date. My first inclination was to use an unsigned short to store each date as a number representing the number of days from (for example) January 1, 2000, to the date in question. Since there are approximately 365.25 days in a year, the range of 65535 days should hold us roughly until the year 2179, which should be good enough for our purposes. Perhaps by that year, we'll all be eating food pills that don't spoil.[4]

[4] Of course, thinking that your program can't possibly last long enough that you need to worry about running off the end of its legal date range is what led to a lot of frantic maintenance work as we approached the year 2000.

However, storing a date as a number of days since a “base date”, such as January 1, 2000, does require a means of translating a human-readable date format like “September 4, 2002” into a number of days from the base date and vice versa. Owing to the peculiarities of our Gregorian calendar (primarily the different numbers of days in different months and the complication of leap years), this is not a trivial matter and is a distraction from our goal here.

However, if we represent a date as a string of the form YYYYMMDD, where YYYY is the year, MM is the month, and DD is the day within the month, we can use the string comparison functions to tell us which of two dates is later than the other one.[5] Here's the analysis:

[5] In case you're wondering why I allocated 4 digits for the year, it was to ensure that the program will work both before and after a change of the century part of the date (e.g., from 1999 to 2000). Unfortunately, not all programmers have been so considerate. Many programs use a 2-digit number to represent the year portion of a date in the form YYMMDD and as a result, behaved oddly during the so-called “Y2K transition”.

  1. Of two dates with different year numbers, whichever has the higher year number is a later date.

  2. Of two dates with the same year number but different month numbers, whichever has the higher month number is a later date.

  3. Of two dates having the same year and month numbers, whichever has the higher day number is a later date.

Because the string comparison operators compare bytes from left to right and stop when a mismatch is detected, as is needed for alphabetical sorting, it should be clear that dates using the representation YYYYMMDD will have their year numbers compared first, followed by the month numbers if needed, followed by the day numbers if needed. Thus, comparing two strings via string::operator > will produce the result true if the “date string” on the left represents a date later than the “date string” on the right, exactly as it should.

Now that we've figured out that we can store the expiration date as a string, how do we arrange for it to be included in the StockItem object? One obvious solution is to make up a new class called, say, DatedStockItem, by copying the interface and implementation from StockItem, adding a new member variable m_Expires, and modifying the copied Reorder member function to take the expiration date into account. However, doing this would create a maintenance problem when we had to make a change that would affect both of these classes, as we'd have to make such a change in two places. Just multiply this nuisance ten or twenty times and you'll get a pretty good idea of how program maintenance has acquired its reputation as difficult and tedious work.[6]

[6] While maintenance has long been a neglected area of computer science, and the programming industry for that matter, that has changed for the better in recent years.

Susan had some questions about this notion of program maintenance:

Susan: What kind of change would you want to make? What is maintenance? What is a typical thing you would want to do to some code?

Steve: Maintenance means any kind of change to a program after it is already being used. For example, if we decide that a short isn't the right type of variable to hold the price, then we have to change its definition to some other type. We'll see lots of examples of maintenance in Chapter 12.

Since one of the purposes of object-oriented programming is to reduce the difficulty of maintaining programs, surely there must be a better way to create a new class “just like” StockItem but with an added member variable and a modified member function to use it.

Reducing Maintenance Via Inheritance

Yes, there is; it's called inheritance. We can define our new class called DatedStockItem with a notation that it inherits (or derives) from StockItem. This makes StockItem the base class (sometimes referred to as the parent class) and our new DatedStockItem class the derived class (sometimes referred to as the child class). By doing this, we are specifying that a DatedStockItem includes every data member and regular member function a StockItem has. Since DatedStockItem is a separate class from StockItem, when we define DatedStockItem we can also add whatever other functions and data we need to handle the differences between StockItem and DatedStockItem.

Susan wanted to clarify some terms:

Susan: Are inheritance and derivation the same thing?

Steve: Yes. To say that B inherits from A is the same as saying that B is derived from A.

She also had some questions about the relationship between the notions of friend and inheritance.

Susan: How about a little reminder about friend here, and how about explaining the difference between friend and inheritance, other than inheritance being an entirely different class. They kinda do the same thing.

Steve: When, in a class definition, you make a function or class a friend of the one you're defining, the friend function or class has access to all members of the class you are defining, disregarding their access specifiers; however, the friend has no other relationship to the class being defined. That is, making class B a friend to class A does not make a B object a substitute for an A object.

On the other hand, if B is (publicly) derived from A, then a B object can be used wherever an A object can be used.

I think a picture might help here. Let's start with a simplified version of the StockItem and DatedStockItem classes, whose interface is shown in Figure 9.7. I recommend that you print out the file that contains these interfaces (codeitema.h) for reference as you go through this section of the chapter.

Figure 9.7. Simplified interface for StockItem and DatedStockItem classes (codeitema.h)
class StockItem
{
public:
  StockItem(std::string Name, short InStock, short MinimumStock);

  void Reorder(std::ostream& os);

protected:
  std::string m_Name;
  short m_InStock;
  short m_MinimumStock;
};

class DatedStockItem: public StockItem // deriving a new class
{
public:
  DatedStockItem(std::string Name, short InStock, short MinimumStock,
  std::string Expires);

  void Reorder(std::ostream& os);

protected:
static std::string Today();

protected:
  std::string m_Expires;
};

Given these definitions, a StockItem object might look as depicted in Figure 9.8.[7]

[7] I'm simplifying by leaving out the internal structure of a string, which affects the actual layout of the object; this detail isn't relevant here.

Figure 9.8. A simplified StockItem object


And a DatedStockItem object might look as depicted in Figure 9.9.

Figure 9.9. A DatedStockItem object


As you can see, an object of the new DatedStockItem class contains a StockItem as part of its data. In this case, that base class part accounts for most of the data of a DatedStockItem; all we've added is a data member called m_Expires. In fact, a derived class object always contains all of the variables and “regular” member functions in the base class because the derived class object effectively has an object of the base class embedded in it, as indicated in Figure 9.9. We can access those member variables and functions that are part of the base class part of our derived class object exactly as though they were defined in the derived class, so long as their access specifiers are either public or protected. Although the public and private access specifiers have been part of our arsenal of tools for some time, this is our first encounter with the protected access specifier. We'll see shortly that the sole purpose of the protected access specifier is to allow derived class member functions to use member functions and variables of the base class part of an object of that derived class, while protecting those member functions and variables from use by unrelated classes.

Susan had some interesting comments and questions about the notion of the base class part of a derived class object.

Susan: When I look at Figure 9.9 I get the feeling that every DatedStockitem object contains a StockItem object; is this the “base class part of the derived class object”?

Steve: Yes.

Susan: The word “derived” is confusing; if a DatedStockItem is derived from a StockItem one tends to take a linear approach as in a family tree, which isn't quite right. It would be better to think of DatedStockItem as a fruit like a plum, with the pit being the StockItem, which is the core of the object.

Steve: Or maybe an onion, where all the layers are edible, rather than making the distinction between the flesh of the fruit and an inedible pit.

Susan: But if so, every member function of the derived class could access every member variable of the base class because “They occur as the base class part of a derived class object”.

Steve: No, as we'll see, even though private members are actually present in the derived class object, they are not accessible to derived class functions. That's why we need protected.

Of course, as noted before, we don't have to rely solely on the facilities we inherit from our base class; we can also add whatever new functions or variables we need to provide the new functionality of the new class. As you will see, we don't want or need to add any public member functions in the present case because our eventual goal is to allow the application programmer to treat objects of the new DatedStockItem class as equivalent to objects of the StockItem class. To reach this goal, these two classes must have the same class interface, i.e., the same public member functions.

A Note on Proper Object-oriented Design

By the way, for proper object-oriented design, it's not enough just to have the same names for the member functions in the derived class; they have to have the same meanings as well. That is, the user shouldn't be surprised by the behavior of a derived class function if he knows how the base class function behaves. For example, if the DatedStockItem Reorder function were to rearrange the items in the inventory rather than generate a reorder report, as the StockItem version does, the user would get very confused! The solution to this problem is simple: make sure that your derived class functions do the “same thing” as the corresponding base class functions, differing only in how they do it.

Overriding a Base class Function

Now let's get back to the implementation of the DatedStockItem class. Instead of adding new public member functions, we will override the base class version of Reorder by writing a new version of Reorder for our DatedStockItem class. Our new function, which has the same signature as that of the base class Reorder function, will use the new data member m_Expires. Since StockItem::Reorder has been overridden by DatedStockItem::Reorder, the latter function will be called whenever the user's program calls the Reorder function of a DatedStockItem.

Susan wasn't sure about the meaning of “overriding” a base class function rather than writing an entirely new one. That discussion led to a more general one about the whole idea of inheritance.

Susan: Why is the term “override” used here? The derived class member function is called for an object of the derived class, so I don't see how it “overrides” the base class member function with the same signature.

Steve: What would happen if we didn't write the derived class function? The base class function would be called. Therefore, the derived class function is overriding the previously existing base class function.

Susan: How does this differ from hiding? Give me an example.

Steve: If we wrote a Reorder function with a different signature in the derived class, such as DatedStockItem::Reorder(int), that would no longer override the base class function, StockItem::Reorder(ostream&), but would hide the base class function. This would mean that the base class function would not be called even if the argument was an ostream. Instead, calling Reorder for a DatedStockItem object with an ostream argument in that case would be an error.

Susan: Ok, I guess I see the difference. But why do you write a new version of Reorder instead of adding a new public member function?

Steve: Precisely because our eventual goal is to allow the user to use stock items with and without dates interchangeably. If StockItem and DatedStockItem had different names for their reordering function, the user would have to call a different function depending on which type the object really was, which would defeat our attempt to make them interchangeable.

Susan: But if the two versions of Reorder were exactly the same, couldn't you just declare them public?

Steve: If they were exactly the same, we wouldn't need two functions in the first place. Reordering works slightly differently for dated than for undated items, so we need two different functions to do the “same” thing in two different ways.

Susan: Yes, but if the names were the same couldn't they be used anywhere just by making them public? I thought this was the whole idea: not to have to rewrite these things.

Steve: They are public. The point is that StockItem::Reorder and DatedStockItem::Reorder accomplish the same result in different ways, so the user of these classes should be able to just call Reorder and get the correct function executed without having to worry about which one that is.

Susan: So is it the expiration date that makes it necessary to make a derived class?

Steve: Yes.

Susan: Is it impossible to extend our old class so it can handle objects both with and without expiration dates rather than making a new class?

Steve: No, it is not impossible to do that. But if we did that, we would be defeating one of the main purposes of object-oriented programming: the ability to divide a task up into pieces that can be handled by different classes that can be maintained more easily. To accomplish that goal, we need two different classes to handle these two different kinds of objects.

Susan: Why can't we just add some new member functions and member variables to a class instead of making a derived class? Are you using inheritance here just to make a point, or is it vital to achieve what we want to achieve?

Steve: If you added more functions, then StockItem would not be StockItem as it is, and needs to be, to handle its original task. You could copy the code for StockItem and then change the copy to handle expiration dates, but that would cause serious maintenance problems later if (when) you had to change the code, because you would have to make the changes in both places. Avoiding such problems was one of the main reasons that C++ was invented.

Susan: Okay, so that explains why we shouldn't add more functions to StockItem but not why we shouldn't add any functions to DatedStockItem.

Steve: Because a DatedStockItem should act just like a StockItem but won't if we add new functions.[8] Instead, we'll write new versions of the ones we already have, like Reorder.

[8] Actually, this is not strictly true. We can add functions to a derived class without affecting how it appears to users, so long as the functions that we add are either private or protected, so that they don't change the public interface of the class. We'll see some examples of this later.

Susan: I still don't understand why you have to write a new version of Reorder. A DatedStockItem is supposed to act just like a StockItem.

Steve: Yes, it is supposed to act “just like” a StockItem, as far as the user of these classes is concerned. However, that means that DatedStockItem has to do the “same” things as StockItem but do them in a different way; in particular, reordering items is different when you have to send things back because their expiration dates have passed. However, this difference in implementation isn't important to the application program, which can treat DatedStockItems just like StockItems.

Before we get into the details of the Reorder function in the DatedStockItem class, I should explain what I mean by “regular member function”. A regular member function is one that is not in any of the following categories:

  1. constructor,

  2. destructor,

  3. assignment operator (operator =).

When we write a derived class (in this case DatedStockItem), it inherits only the regular member functions, not the constructor, destructor, or operator = functions, from the base class (in this case StockItem). Instead, we have to write our own derived class versions of these functions if we don't want to rely on the compiler-generated versions in the derived class.

It may not be obvious why we have to write our own versions of these functions. It wasn't to Susan:

Susan: So in this case our derived class DatedStockitem doesn't inherit the constructor, destructor, and assignment operator because it takes an object of the Stockitem class and combines it with a new member variable m_Expires to make an object of the derived DatedStockItem class. But if the only differences between the two classes are in the implementation of the “regular member functions” then the default constructor, after the inheritance of the base class, should have no problem making a new derived class object because it won't contain any new member variables.

Steve: You're right: that would be possible in such a case, but not in this case, because we are adding a new member variable. However, the code in the base class functions isn't wasted in any event because the base class constructor, destructor, and operator = functions are used automatically in the implementation of the corresponding derived class functions.

Susan: But what if they are similar to the derived class functions that do the same thing? Can't you use them then?

Steve: In the case of the base class constructor and destructor, you actually do use them indirectly; the compiler will always call a base class constructor when a derived class constructor is executed, and it will always call the base class destructor when the derived class destructor is executed.[9] Similarly, the derived class assignment operator will call the base class assignment operator to copy the base class part of the derived class object. However, any new member variables added to the derived class will have to be handled in the derived class functions.

[9] When we write a derived class constructor, the base class default constructor is called to initialize the base class part of the object if we don't say which base class constructor we want; however, we can tell the compiler explicitly to call a different base class constructor.

Susan: So, anything not derived that is added to a derived class has to be handled as a separate entity from the stuff in the base class part of the derived class? UGH!

Steve: Yes, but just the newly added data has to be handled separately; the inherited data from the base class is handled by the base class functions. Someone has to write the code to handle the new member variables; the compiler can't read our minds!

We won't be wasting any effort when writing the derived class versions of the constructors, destructor, and assignment operator, because the base class versions of these functions are called automatically to construct, destroy, and assign the base class part of the derived class object. Therefore, we can concentrate on the newly added parts of that class. We'll see exactly how and when these base class functions are called as we go through the corresponding derived class functions.[10]

[10] It may seem that this automatic calling of base class functions for the constructor, destructor, and assignment operator is a type of inheritance. However, it really isn't because those functions are applied to the base class part of the derived class object, not to the derived class object itself. In the same way, you can refer to base class functions explicitly by qualifying the function name with the class name. This is also not a case of inheritance because these functions are applied only to the base class part.

For the moment, we won't have to define any of these derived class functions except two new constructors. Since the member variables and the base class part of DatedStockItem are all concrete data types, the compiler-generated versions of the destructor and assignment operator, which call the destructors and assignment operators for those member variables (and the base class part), work perfectly well.[11]

[11] Here's a little more detail on how the compiler-generated assignment operator works. It assigns all of the members of the source variable to the destination variable (using their assignment operators if they are user-defined types) and uses the assignment operator of the base class to copy the base class part.

Susan didn't let this “compiler-generated” stuff slip by without a bit of an argument:

Susan: About this statement that “compiler-generated versions of the destructor and assignment operators work perfectly well”: Since when were destructors compiler-generated? I thought only assignment operators could be compiler-generated. You are holding out on me.

Steve: Every class, by default, has a default constructor, copy constructor, assignment operator, and destructor. Any of these that we don't mention in our interface will automatically be generated by the compiler.

She also had some questions about concrete data types:

Susan: Aren't the member variables of a class always concrete data types (i.e., variables that act like native data types)? I thought a concrete data type was “a class whose objects behave like native data types”.

Steve: Well, if every class defined a concrete data type, we wouldn't need a separate name for that concept, would we? As this suggests, it's entirely possible to have member variables that aren't concrete data types. In particular, pointers aren't concrete data types, because copying them doesn't actually copy their data, only the address of the data to which they refer. That's part of what makes them so tricky to work with.

Before we can use StockItem as a base class, however, there is one change we're going to make to our previous definition of StockItem to make it work properly in that application; namely, we have to change the access specifier for its member variables from private to protected. By this point, you should be familiar with the meaning of private. Any member variables or member functions that are marked private can be referred to only by member functions of the same class; all other functions are denied access to them. On the other hand, when we mark member functions or member data of a class as public, we are specifying that any function, whether or not a member function of the class in question, can access them. That seems to take care of all the possibilities, so what is protected good for?

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset