11.5. Case Study: Payroll System Class Hierarchy Using PolymorphismThis section reexamines the CommissionEmployeeBasePlusCommissionEmployee hierarchy that we explored in Section 10.4. Now we use an abstract method and polymorphism to perform payroll calculations based on the type of employee. We create an enhanced employee hierarchy to solve the following problem:
We use abstract class Employee to represent the general concept of an employee. The classes that inherit from Employee are SalariedEmployee, CommissionEmployee and HourlyEmployee. Class BasePlusCommissionEmployee inherits from CommissionEmployee. The UML class diagram in Fig. 11.2 shows our polymorphic employee inheritance hierarchy. Note that abstract class name Employee is italicized, as per UML convention; concrete class names are not italicized. Figure 11.2. Employee hierarchy UML class diagram.Abstract base class Employee declares the "interface" to the hierarchythat is, the set of methods that a program can invoke on all Employee objects. We use the term "interface" here in a general sense to refer to the various ways programs can communicate with objects of any Employee derived class. Be careful not to confuse the general notion of an "interface" with the formal notion of a Visual Basic interface, the subject of Section 11.7. Each employee, regardless of the way his or her earnings are calculated, has a first name, a last name and a social security number, so Public properties FirstName, LastName and SocialSecurityNumber appear in abstract base class Employee. The following sections implement the Employee class hierarchy. The first five sections show the abstract base class Employee and the concrete derived classes SalariedEmployee, CommissionEmployee, HourlyEmployee and the indirectly derived concrete class BasePlusCommissionEmployee. The last section shows a test program that builds objects of these classes and processes the objects polymorphically. 11.5.1. Creating Abstract Base Class EmployeeIn this example, class Employee will provide methods CalculateEarnings and ToString, and properties that manipulate an Employee's instance variables. A CalculateEarnings method certainly applies generically to all employees. But each earnings calculation depends on the employee's class. So we declare CalculateEarnings as MustOverride in base class Employee because a default implementation does not make sense for that method there is not enough information to determine what amount CalculateEarnings should return. Each derived class overrides CalculateEarnings with an appropriate implementation. To calculate an employee's earnings, the program assigns to a base class Employee variable a reference to the employee's object, then invokes the CalculateEarnings method on that variable. We will maintain an array of Employee variables, each of which holds a reference to an Employee object (of course, there cannot be Employee objects because Employee is an abstract classthanks to inheritance, however, all objects of all derived classes of Employee may nevertheless be thought of as Employee objects). The program iterates through the array and calls method CalculateEarnings for each Employee object. Visual Basic processes these method calls polymorphically. Including abstract method CalculateEarnings in class Employee forces every directly derived concrete class of Employee to override CalculateEarnings. This enables the designer of the class hierarchy to demand that each derived concrete class provide an appropriate pay calculation. Method ToString in class Employee returns a String containing the first name, last name and social security number of the employee. As we will see, each derived class of Employee overrides method ToString to create a string representation of an object of that class that contains the employee's type (e.g., "salaried employee:") followed by the rest of the employee's information. The diagram in Fig. 11.3 shows the five classes of the hierarchy down the left side and methods CalculateEarnings and ToString across the top. For each class, the diagram shows the desired results of each method. [Note: We do not list base class Employee's properties because they are not overridden in any of the derived classeseach of these properties is inherited and used "as is" by each of the derived classes.] Figure 11.3. Polymorphic interface for the Employee hierarchy classes.Class Employee (Fig. 11.4) includes a constructor that takes the first name, last name and social security number as arguments (lines 914); Get accessors that return the first name, last name and social security number (lines 1820, 2931 and 4042, respectively); Set accessors that set the first name, last name and social security number (lines 2224, 3335 and 4446, respectively); method ToString (lines 5053), which returns the string representation of an Employee; and abstract (MustOverride) method CalculateEarnings (line 56), which must be implemented by concrete derived classes. Note that the SocialSecurityNumber property does not validate the social security number in this example. In business-critical applications, such validation should be provided. Figure 11.4. Employee abstract base class.
Why did we decide to declare CalculateEarnings as a MustOverride method? It simply does not make sense to provide an implementation of this method in class Employee. We cannot calculate the earnings for a general Employeewe first must know the specific Employee type to determine the appropriate earnings calculation. By declaring this method MustOverride, we indicate that each concrete derived class must provide an appropriate CalculateEarnings implementation and that a program will be able to use base class Employee variables to invoke method CalculateEarnings polymorphically for any type of Employee. 11.5.2. Creating Concrete Derived Class SalariedEmployeeClass SalariedEmployee (Fig. 11.5) inherits class Employee (line 4) and overrides CalculateEarnings (lines 3133), which makes SalariedEmployee a concrete class. The class includes a constructor (lines 913) that takes a first name, a last name, a social security number and a weekly salary as arguments; a WeeklySalary property that has a Get accessor (lines 1719) to return weeklySalaryValue's value and a Set accessor (lines 2127) to assign a new non-negative value to instance variable weeklySalaryValue; a method CalculateEarnings (lines 3133) to calculate a SalariedEmployee's earnings; and a method ToString (lines 3639) that returns a String including the employee's type, namely, "salaried employee: ", followed by employee-specific information produced by base class Employee's ToString method, and the value of SalariedEmployee's WeeklySalary property. Class SalariedEmployee's constructor passes the first name, last name and social security number to base class Employee's constructor (line 11). Method CalculateEarnings overrides abstract method CalculateEarnings of Employee with a concrete implementation that returns the SalariedEmployee's weekly salary. If we do not implement CalculateEarnings, class SalariedEmployee must be declared MustOverrideotherwise, a compilation error occurs (and, of course, we want SalariedEmployee to be a concrete class). Figure 11.5. SalariedEmployee class derived from class Employee.
SalariedEmployee's ToString method (lines 3639) overrides Employee method ToString. If class SalariedEmployee did not override ToString, the class would have inherited Employee's ToString method. In that case, SalariedEmployee's ToString method would simply return the employee's full name and social security number, which does not adequately represent a SalariedEmployee. To produce a complete string representation of a SalariedEmployee, the derived class's ToString method returns "salaried employee: " followed by the base class Employee-specific information (i.e., first name, last name and social security number) obtained by invoking the base class's ToString (line 37)a nice example of code reuse. The string representation of a SalariedEmployee also contains the employee's weekly salary obtained from the WeeklySalary property. 11.5.3. Creating Concrete Derived Class HourlyEmployeeClass HourlyEmployee (Fig. 11.6) also inherits class Employee (line 4). The class includes a constructor (lines 1016) that takes as arguments a first name, a last name, a social security number, an hourly wage and the number of hours worked. Lines 2430 and 3945 declare Set accessors that assign new values to instance variables wageValue and hours-Value, respectively. The Set accessor of property Wage (lines 2430) ensures that wageValue is non-negative, and the Set accessor of property Hours (lines 3945) ensures that hoursValue is between 0 and 168 (the total number of hours in a week). Properties Wage and Hours also include Get accessors (lines 2022 and 3537) to return the values of wageValue and hoursValue, respectively. Method CalculateEarnings (lines 4955) calculates an HourlyEmployee's earnings. Method ToString (lines 5862) returns the employee's type, namely, "hourly employee: ", and employee-specific information, including the full name, the social security number, and the values of properties Wage and Hours. Note that the HourlyEmployee constructor, like the SalariedEmployee constructor, passes the first name, last name and social security number to the base class Employee constructor (line 13). In addition, method ToString calls base class method ToString (line 59) to obtain the Employee-specific information (i.e., first name, last name and social security number)this is another nice example of code reuse. Figure 11.6. HourlyEmployee class derived from class Employee.
11.5.4. Creating Concrete Derived Class CommissionEmployeeClass CommissionEmployee (Fig. 11.7) inherits class Employee (line 4). The class includes a constructor (lines 1015) that takes a first name, a last name, a social security number, a sales amount and a commission rate; Get accessors (lines 1921 and 3436) that retrieve the values of instance variables grossSalesValue and commissionRateValue, respectively; Set accessors (lines 2329 and 3844) that assign new values to these instance variables; method CalculateEarnings (lines 4850) to calculate a CommissionEmployee's earnings; and method ToString (lines 5357), which returns the employee's type, namely, "commission employee: " and employee-specific information, including the full name and social security number, and the values of properties GrossSales and CommissionRate. The CommissionEmployee's constructor also passes the first name, last name and social security number to the Employee constructor (line 12) to initialize Employee's Private instance variables. Method ToString calls base class method ToString (line 54) to obtain the Employee-specific information (i.e., first name, last name and social security number). Figure 11.7. CommissionEmployee class derived from Employee.
11.5.5. Creating Indirect Concrete Derived Class BasePlusCommissionEmployeeClass BasePlusCommissionEmployee (Fig. 11.8) inherits class CommissionEmployee (line 4) and therefore is an indirect derived class of class Employee. Class BasePlusCommissionEmployee has a constructor (lines 914) that takes as arguments a first name, a last name, a social security number, a sales amount, a commission rate and a base salary. It then passes the first name, last name, social security number, sales amount and commission rate to the CommissionEmployee constructor (line 12) to initialize the inherited members. BasePlus-CommissionEmployee also contains a property BaseSalary whose Set accessor (lines 2228) assigns a new value to instance variable baseSalaryValue, and whose Get accessor (lines 1820) returns baseSalaryValue. Method CalculateEarnings (lines 3234) calculates a BasePlusCommissionEmployee's earnings. Note that line 33 in method CalculateEarnings calls base class CommissionEmployee's CalculateEarnings method to calculate the commission-based portion of the employee's earnings. This is another nice example of code reuse. BasePlusCommissionEmployee's ToString method (lines 3740) creates a String representation of a BasePlusCommissionEmployee that contains "basesalaried", followed by the String obtained by invoking base class CommissionEmployee's ToString method (another example of code reuse), then the base salary. The result is a String beginning with "base-salaried commission employee" followed by the rest of the BasePlusCommissionEmployee's information. Recall that CommissionEmployee's ToString method obtains the employee's first name, last name and social security number by invoking the ToString method of its base class (i.e., Employee)another example of code reuse. Note that BasePlusCommissionEmployee's ToString initiates a chain of method calls that spans all three levels of the Employee hierarchy. Figure 11.8. BasePlusCommissionEmployee derived from CommissionEmployee.
11.5.6. Demonstrating Polymorphic Processing, Expression TypeOf...Is, TryCast and DowncastingTo test our Employee hierarchy, the program in Fig. 11.9 creates an object of each of the four concrete classes SalariedEmployee, HourlyEmployee, CommissionEmployee and BasePlusCommissionEmployee. The program manipulates these objects, first via variables of each object's own type, then polymorphically, using an array of Employee variables. While processing the objects polymorphically, the program increases the base salary of each BasePlusCommissionEmployee by 10%this requires that the program determine each object's type at execution time. Finally, the program polymorphically determines and outputs the type of each object in the Employee array. Lines 613 create objects of each of the four concrete Employee derived classes. Lines 1628 output (non-polymorphically) the string representation and earnings of each of these objects. Figure 11.9. Employee class hierarchy test program.
Lines 3132 create and initialize array employees with four Employees. This statement is valid because, through inheritance, a SalariedEmployee is an Employee, an HourlyEmployee is an Employee, a CommissionEmployee is an Employee and a BasePlusCommissionEmployee is an Employee. Therefore, we can assign the references of SalariedEmployee, HourlyEmployee, CommissionEmployee and BasePlusCommissionEmployee objects to base class Employee variables, even though Employee is an abstract class. Lines 3755 iterate through array employees and invoke methods ToString and CalculateEarnings with Employee variable currentEmployee, which is assigned the reference to a different Employee in the array during each iteration. The output illustrates that the appropriate methods for each class are indeed invoked. All calls to method ToString and CalculateEarnings are resolved at execution time, based on the type of the object to which currentEmployee refers. This process is known as late binding. For example, line 38 explicitly invokes method ToString of the object to which currentEmployee refers. As a result of late binding, Visual Basic decides which class's ToString method to call at execution time rather than at compile time. Recall that only the methods of class Employee can be called via an Employee variable (and Employee, of course, includes the methods of class Object). (Section 10.7 discussed the set of methods that all classes inherit from class Object.) A base class reference can be used to invoke only methods of the base class, even though those method calls polymorphically invoke derived class method implementations when sent to derived class objects. Using Expression TypeOf...Is to Determine Object TypeWe perform special processing on BasePlusCommissionEmployee objectsas we encounter these objects, we increase their base salary by 10%. When processing objects polymorphically, we typically do not need to worry about the specifics, but to adjust the base salary, we do have to determine the specific type of Employee object at execution time. Line 41 uses a TypeOf...Is expression to determine whether a particular Employee object's type is BasePlusCommissionEmployee. The condition in line 41 is true if the object referenced by currentEmployee is a BasePlusCommissionEmployee. This would also be true for any object of a BasePlusCommissionEmployee derived class (if there are any) because of the is-a relationship a derived class has with its base class. Using TRyCast to Downcast from a Base Class to a Derived Class TypeLines 4445 use keyword tryCast to downcast currentEmployee from base class type Employee to derived class type BasePlusCommissionEmployeethis cast returns an object only if the object has an is a relationship with type BasePlusCommissionEmployee. The condition at line 41 ensures that this is the case. If the is a relationship does not exist, TRyCast returns Nothing. This cast is required if we are to access derived class BasePlusCommissionEmployee property BaseSalary on the current Employee objectas you will see momentarily, attempting to invoke a derived-class-only method or property directly on a base class reference is a compilation error. Note that we did not need to use both TypeOf...Is and TRyCast. We could have simply used tryCast, then tested the value of variable employee to ensure that it was not Nothing before attempting to execute the statements in lines 4750. We used both here so that we could demonstrate the new Visual Basic 2005 tryCast keyword. Common Programming Error 11.3
Error-Prevention Tip 11.1
When downcasting an object, tryCast returns Nothing if at execution time the object being converted does not have an is-a relationship with the target type. An object can be cast only to its own type or to one of its base class types; otherwise, a compilation error occurs. If the Typeof...Is expression in line 41 is true, the body of the If statement (lines 4450) performs the special processing required for the BasePlusCommissionEmployee object. Using BasePlusCommissionEmployee variable employee, lines 4750 access derived-class-only property BaseSalary to retrieve and update the employee's base salary with the 10% raise. Lines 5354 invoke method CalculateEarnings on currentEmployee, which calls the appropriate derived class object's CalculateEarnings method polymorphically. Note that obtaining the earnings of the SalariedEmployee, HourlyEmployee and CommissionEmployee polymorphically in lines 5354 produces the same result as obtaining these employees' earnings non-polymorphically in lines 1625. However, the earnings amount obtained for the BasePlusCommissionEmployee in lines 5354 is higher than that obtained in lines 2628, due to the 10% increase in its base salary. Lines 5861 display each employee's type as a string. Every object in Visual Basic knows its own class and can access this information through method GetType, which all classes inherit from class Object. Figure 10.20 lists the Object methods that are inherited directly or indirectly by all classes. Method GetType returns an object of class Type (namespace System), which contains information about the object's type, including its fully qualified class name. For more information on the Type class, please read msdn.microsoft.com/library/en-us/cpref/html/frlrfsystemtypeclasstopic.asp. Line 60 invokes method GetType on the object to get a Type object that represents the object's type at execution time. Then line 60 uses the FullName property of the object returned by GetType to get the class's name fully qualified name (see the last four lines of the program's output). Notes on DowncastingWe avoid several compilation errors by downcasting an Employee variable to a BasePlus-CommissionEmployee variable in lines 4445. If we remove the cast expression from line 45 and attempt to assign Employee variable currentEmployee directly to BasePlusCommissionEmployee variable employee, we would receive a compilation error (when Option Strict is On). This error indicates that the attempt to assign the base class variable current-Employee to derived class variable basePlusCommissionEmployee is not allowed. The compiler prevents this assignment because a CommissionEmployee is not a BasePlus-CommissionEmployeethe is-a relationship applies only between the derived class and its base classes, not vice versa. Similarly, if lines 47 and 50 were to use base class variable currentEmployee, rather than derived class variable employee, to access derived-class-only property BaseSalary, we would receive a compilation error on each of these lines. Attempting to invoke derivedclass-only methods and properties on a base class reference is not allowed. While lines 47 and 50 execute only if the Typeof...If expression in line 41 returns true to indicate that currentEmployee has been assigned a reference to a BasePlusCommissionEmployee object, we cannot attempt to invoke derived class BasePlusCommissionEmployee property BaseSalary on base class Employee reference currentEmployee. The compiler would generate errors in lines 47 and 50, because property BaseSalary is not a base class property and cannot be accessed using a base class variable. Although the actual method or property that is called depends on the object's type at execution time, a variable can be used to invoke only those methods and properties that are members of that variable's type, which the compiler verifies. Using a base class Employee variable, we can invoke only methods and properties found in class Employee (Fig. 11.4)methods CalculateEarnings and ToString, and properties FirstName, LastName and SocialSecurityNumber. 11.5.7. Summary of the Allowed Assignments between Base Class and Derived Class VariablesNow that you have seen a complete application that processes diverse derived class objects polymorphically, we summarize what you can and cannot do with base class and derived class objects and variables. Although a derived class object also is a base class object, the two objects are nevertheless different. As discussed previously, derived class objects can be treated as if they are base class objects. However, the derived class can have additional derived-class-only members. For this reason, assigning a base class reference to a derived class variable is not allowed without a castsuch an assignment would leave the derived-class-only members undefined for the base class object. We have discussed four ways to assign base class and derived class references to variables of base class and derived class types:
|