13.1. Introduction
In Chapters
9–12, we discussed key object-oriented programming
technologies including classes, objects, encapsulation, operator overloading and
inheritance. We now continue our study of OOP by explaining and demonstrating
polymorphism
with inheritance hierarchies. Polymorphism enables us to "program in the
general" rather than "program in the specific." In particular, polymorphism
enables us to write programs that process objects of classes that are part of
the same class hierarchy as if they were all objects of the hierarchy's base
class. As we'll soon see, polymorphism works off base-class pointer handles and
base-class reference handles, but not off name handles.
Consider the following example of
polymorphism. Suppose we create a program that simulates the movement of several
types of animals for a biological study. Classes Fish, Frog and
Bird represent the three types of animals under
investigation. Imagine that each of these classes inherits from base class
Animal, which contains a function
move and maintains an animal's current
location. Each derived class implements function move. Our program maintains a vector of pointers
to objects of the various Animal derived
classes. To simulate the animals' movements, the program sends each object the
same message once per second—namely, move. However, each specific type
of Animal responds to a move message in its own unique way—a
Fish might swim two feet, a Frog might jump three feet and a
Bird might fly ten feet. The program issues
the same message (i.e., move) to each animal
object generically, but each object knows how to modify its location
appropriately for its specific type of movement. Relying on each object to know
how to "do the right thing" (i.e., do what is appropriate for that type of
object) in response to the same function call is the key concept of
polymorphism. The same message (in this case, move) sent to a variety
of objects has "many forms" of results—hence the term polymorphism.
With polymorphism, we can design and
implement systems that are easily extensible—new classes can be added with
little or no modification to the general portions of the program, as long as the
new classes are part of the inheritance hierarchy that the program processes
generically. The only parts of a program that must be altered to accommodate new
classes are those that require direct knowledge of the new classes that you add
to the hierarchy. For example, if we create class Tortoise that inherits from class Animal
(which might respond to a move message by
crawling one inch), we need to write only the Tortoise class and the part of the simulation that instantiates a
Tortoise object. The portions of the
simulation that process each Animal generically can remain the
same.
We begin with a sequence of small, focused
examples that lead up to an understanding of virtual functions and dynamic binding—polymorphism's two
underlying technologies. We then present a case study that revisits Chapter
12's Employee hierarchy. In the case study,
we define a common "interface" (i.e., set of functionality) for all the classes
in the hierarchy. This common functionality among employees is defined in a
so-called abstract base class, Employee, from which classes
SalariedEmployee, HourlyEmployee and CommissionEmployee
inherit directly and class BaseCommissionEmployee inherits indirectly.
We'll soon see what makes a class "abstract" or its opposite—"concrete."
In this hierarchy, every employee has an earnings
function to calculate the employee's weekly pay. These earnings
functions vary by employee type—for instance, SalariedEmployees are paid a fixed weekly salary regardless of the number
of hours worked, while HourlyEmployees are paid by
the hour and receive overtime pay. We show how to process each employee "in the
general"—that is, using base-class pointers to call the earnings function of several derived-class objects. This way, you
need to be concerned with only one type of function call, which can be used to
execute several different functions based on the objects referred to by the
base-class pointers.
A key feature of this chapter is its
(optional) detailed discussion of polymorphism, virtual functions and dynamic binding "under the hood,"
which uses a detailed diagram to explain how polymorphism can be implemented in
C++.
Occasionally, when performing polymorphic
processing, we need to program "in the specific," meaning that operations need
to be performed on a specific type of object in a hierarchy—the operation cannot
be generally applied to several types of objects. We reuse our Employee
hierarchy to demonstrate the powerful capabilities of runtime type information (RTTI) and dynamic casting, which
enable a program to determine the type of an object at execution time and act on
that object accordingly. We use these capabilities to determine whether a
particular employee object is a BasePlusCommissionEmployee, then give that employee a 10 percent bonus on his or her
base salary.