11.3. Demonstrating Polymorphic BehaviorSection 10.4 created a commission employee class hierarchy, in which class BasePlusCommissionEmployee inherited from class CommissionEmployee. The examples in that section manipulated CommissionEmployee and BasePlusCommissionEmployee objects by using references to them to invoke their methods. We aimed base class references at base class objects and derived class references at derived class objects. These assignments are natural and straightforwardbase class references are intended to refer to base class objects, and derived class references are intended to refer to derived class objects. However, as you will soon see, some "crossover" assignments are possible. In the next example, we aim a base class reference at a derived class object. We then show how invoking a method on a derived class object via a base class reference invokes the derived class functionalitythe type of the actual referenced object, not the type of the reference, determines which method is called. This example demonstrates the key concept that an object of a derived class can be treated as an object of its base class, yet still "do the right thing." This enables various interesting manipulations. A program can create an array of base class references that refer to objects of many derived class types. This is allowed because each derived class object is an object of its base class. For instance, we can assign the reference of a BasePlusCommissionEmployee object to a base class CommissionEmployee variable because a BasePlusCommissionEmployee is a CommissionEmployee so we can treat a BasePlusCommissionEmployee as a CommissionEmployee. As you will learn later in the chapter, we cannot treat a base class object as a derived class object because a base class object is not an object of any of its derived classes. For example, we cannot assign the reference of a CommissionEmployee object to a derived class BasePlusCommissionEmployee variable because a CommissionEmployee is not a BasePlusCommissionEmployeea CommissionEmployee does not have a baseSalary instance variable and does not have property BaseSalary. The is-a relationship applies only from a derived class to its direct (and indirect) base classes, but not vice versa. The compiler does allow the assignment of a base class reference to a derived class variable if we explicitly cast the base class reference to the derived class typea technique we discuss in detail in Section 11.5. Why would we ever want to perform such an assignment? A base class reference can be used to invoke only the methods declared in the base class attempting to invoke derived-class-only methods through a base class reference results in compilation errors. If a program needs to perform a derived class-specific operation on a derived class object referenced by a base class variable, the program must first cast the base class reference to a derived class reference through a technique known as downcasting. This enables the program to invoke derived class methods that are not in the base class, but only off a derived class reference. We demonstrate downcasting in Section 11.5. Figure 11.1 shows three ways to use base class and derived class variables to store references to base class and derived class objects. The first two are straightforwardas in Section 10.4, we assign a base class reference to a base class variable, and we assign a derived class reference to a derived class variable. Then we demonstrate the relationship between derived classes and base classes (i.e., the is-a relationship) by assigning a derived class reference to a base class variable. [Note: This program uses the CommissionEmployee and BasePlusCommissionEmployee classes from Fig. 10.13 and Fig. 10.14, respectively.] Figure 11.1. Assigning base class and derived class references to base class and derived class variables.
In Fig. 11.1, lines 1011 create a CommissionEmployee object and assign its reference to a CommissionEmployee variable. Lines 1415 create a BasePlusCommissionEmployee object and assign its reference to a BasePlusCommissionEmployee variable. These assignments are naturalfor example, a CommissionEmployee variable's primary purpose is to hold a reference to a CommissionEmployee object. Lines 1820 use reference commissionEmployee1 to invoke ToString explicitly. Because commissionEmployee1 refers to a CommissionEmployee object, base class CommissionEmployee's version of ToString is called, as is evident from the output. Similarly, lines 2426 use basePlusCommissionEmployee to invoke ToString explicitly on the BasePlusCommissionEmployee object. This invokes derived class BasePlusCommissionEmployee's version of ToString, as is also evident from the output. Lines 2930 then assign to a base class CommissionEmployee variable commissionEmployee2 the reference to derived class object basePlusCommissionEmployee, which lines 3335 use to invoke method ToString. A base class variable that contains a reference to a derived class object and that is used to call a method (which is in both the base class and the derived class) actually calls the derived class version of the method (polymorphically). Hence, commissionEmployee2.ToString() in line 35 actually calls class BasePlusCommissionEmployee's ToString method. The compiler allows this "crossover" because an object of a derived class is an object of its base class (but not vice versa). When the compiler encounters a method call made through a variable, it determines whether the method can be called by checking the variable's class type. If that class contains the proper method declaration (or inherits one), the compiler allows the call to be compiled. At execution time, the type of the object to which the variable refers determines the actual version of the method to use. This is polymorphic behavior. |