Factoring and Inheritance in C++ - dummies

Factoring and Inheritance in C++

By Stephen R. Davis

The concept of inheritance, and thus factoring, in C++ allows one class to inherit the properties of a base class. Inheritance has a number of purposes; the main benefit of inheritance is the ability to point out the relationship between classes. This is the so-called IS_A relationship — a MicrowaveOven IS_A Oven and stuff like that.

Factoring is great stuff if you make the correct correlations. For example, the microwave versus conventional oven relationship seems natural. Claim that microwave is a special kind of toaster, and you’re headed for trouble. True, they both make things hot, they both use electricity, and they’re both found in the kitchen, but the similarity ends there — a microwave can’t make toast and a toaster can’t make nachos.

Identifying the classes inherent in a problem and drawing the correct relationships among these classes is a process known as factoring. (The word is related to the arithmetic that you were forced to do in grade school: factoring out the least common denominators, for example, 12 is equal to 2 times 2 times 3.)

Here’s how you can use inheritance to simplify your programs using a bank account example. Suppose that you were asked to write a simple bank program that implemented the concept of a savings account and a checking account.

Object-oriented programmers have come up with a concise way to describe the salient points of a class in a drawing. The Checking and Savings classes are shown in this figure. (This is only one of several ways to graphically express the same thing.)

To read this figure and the other figures, remember the following:

  • The big box is the class, with the class name at the top.

  • The names in boxes are member functions.

  • The names not in boxes are data members.

  • The names that extend partway out of the boxes are publicly accessible members; that is, these members can be accessed by functions that are not part of the class or any of its descendents. Those members that are completely within the box are not accessible from outside the class.

  • A thick arrow represents the IS_A relationship.

  • A thin arrow represents the HAS_A relationship.

A Car IS_A Vehicle, but a Car HAS_A Motor.

You can see in the first figure that the Checking and Savings classes have a lot in common. For example, both classes have a withdrawal() and deposit() member function. Because the two classes aren’t identical, however, they must remain as separate classes. (In a real-life bank application, the two classes would be a good deal more different than in this example.) Still, there should be a way to avoid this repetition.

You could have one of these classes inherit from the other. Savings has more members than Checking, so you could let Savings inherit from Checking. This arrangement is shown in this next figure.

The Savings class inherits all the members. The class is completed with the addition of the data member noWithdrawals and by overriding the function withdrawal(). You have to override withdrawal() because the rules for withdrawing money from a savings account are different from those for withdrawing money from a checking account.

Although letting Savings inherit from Checking is laborsaving, it’s not completely satisfying. The main problem is that, like the weight listed on my driver’s license, it misrepresents the truth. This inheritance relationship implies that a savings account is a special type of checking account, which it is not.

Such misrepresentations are confusing to the programmer, both today’s and tomorrow’s. Someday, a programmer unfamiliar with our programming tricks will have to read and understand what our code does. Misleading representations are difficult to reconcile and understand.

In addition, such misrepresentations can lead to problems down the road. Suppose, for example, that the bank changes its policies with respect to checking accounts. Say it decides to charge a service fee on checking accounts only if the minimum balance dips below a given value during the month.

A change like this can be easily handled with minimal changes to the class Checking. You’ll have to add a new data member to the class Checking to keep track of the minimum balance during the month. Let’s go out on a limb and call it minimumBalance.

But now you have a problem. Because Savings inherits from Checking, Savings gets this new data member as well. It has no use for this member because the minimum balance does not affect savings accounts, so it just sits there. Remember that every checking account object has this extra minimumBalance member. One extra data member may not be a big deal, but it adds further confusion.

Changes like this accumulate. Today it’s an extra data member — tomorrow it’s a changed member function. Eventually, the savings account class is carrying a lot of extra baggage that is applicable only to checking accounts.

Now the bank comes back and decides to change some savings account policy. This requires you to modify some function in Checking. Changes like this in the base class automatically propagate down to the subclass unless the function is already overridden in the subclass Savings.

For example, suppose that the bank decides to give away toasters for every deposit into the checking account. Without the bank (or its programmers) knowing it, deposits to checking accounts would automatically result in toaster donations. Unless you’re very careful, changes to Checking may unexpectedly appear in Savings.

How can you avoid these problems? Claiming that Checking is a special case of Savings changes but doesn’t solve our problem. What you need is a third class (call it Account, just for grins) that embodies the things that are common between Checking and Savings, as shown here.

How does building a new account solve the problems? First, creating a new Account class is a more accurate description of the real world (whatever that is). Of course, there really is something known as an account. Savings accounts and checking accounts are special cases of this more fundamental concept.

In addition, the class Savings is insulated from changes to the class Checking (and vice versa). If the bank institutes a fundamental change to all accounts, you can modify Account, and all subclasses will automatically inherit the change. But if the bank changes its policy only for checking accounts, you can modify just the Checking account class without affecting Savings.

This process of culling common properties from similar classes is the essence of class factoring.

Factoring is legitimate only if the inheritance relationship corresponds to reality. Factoring together a class Mouse and Joystick because they’re both hardware pointing devices is legitimate. Factoring together a class Mouse and Display because they both make low-level operating system calls is not.