Section 11.5. Case Study: Payroll System Class Hierarchy Using Polymorphism


11.5. Case Study: Payroll System Class Hierarchy Using Polymorphism

This 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:

A company pays its employees on a weekly basis. The employees are of four types: Salaried employees are paid a fixed weekly salary regardless of the number of hours worked, hourly employees are paid by the hour and receive overtime pay (1.5 times the regular hourly salary) for all hours worked in excess of 40 hours, commission employees are paid a percentage of their sales, and base-plus-commission employees receive a base salary plus a percentage of their sales. For the current pay period, the company has decided to reward base-plus-commission employees by adding 10% to their base salaries. The company wants to implement a Visual Basic application that performs its payroll calculations polymorphically.

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 Employee

In 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.

 1  ' Fig. 11.4: Employee.vb  2  ' Employee abstract base class.  3  Public MustInherit Class Employee  4     Private firstNameValue As String  5     Private lastNameValue As String  6     Private socialSecurityNumberValue As String  7  8     ' three-argument constructor  9     Public Sub New(ByVal first As String, ByVal last As String, _ 10        ByVal ssn As String) 11        FirstName = first 12        LastName = last 13        SocialSecurityNumber = ssn 14     End Sub ' New 15 16     ' property FirstName 17     Public Property FirstName() As String 18        Get 19           Return firstNameValue 20        End Get 21 22        Set(ByVal first As String) 23           firstNameValue = first 24        End Set 25     End Property ' FirstName 26 27     ' property LastName 28     Public Property LastName() As String 29        Get 30           Return lastNameValue 31        End Get 32 33        Set(ByVal last As String) 34           lastNameValue = last 35        End Set 36     End Property ' LastName 37 38     ' property SocialSecurityNumber 39     Public Property SocialSecurityNumber() As String 40        Get 41           Return socialSecurityNumberValue 42        End Get 43 44        Set(ByVal ssn As String) 45           socialSecurityNumberValue = ssn 46        End Set 47     End Property ' SocialSecurityNumber 48 49     ' return String representation of Employee object 50     Public Overrides Function ToString() As String 51        Return (String.Format("{0} {1}" , FirstName, LastName) & vbCrLf & _ 52           "social security number: " & SocialSecurityNumber) 53     End Function ' ToString 54 55     ' abstract method overridden by derived class               56     Public MustOverride Function CalculateEarnings() As Decimal 57  End Class ' Employee

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 SalariedEmployee

Class 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.

 1  ' Fig. 11.5: SalariedEmployee.vb  2  ' SalariedEmployee class inherits Employee  3  Public Class SalariedEmployee  4     Inherits Employee           5  6     Private weeklySalaryValue As Decimal ' employee's weekly salary  7  8     ' four-argument constructor  9     Public Sub New (ByVal first As String, ByVal last As String, _ 10        ByVal ssn As String, ByVal salary As Decimal) 11        MyBase.New(first, last, ssn) ' pass to Employee constructor 12        WeeklySalary = salary ' validate and store salary 13     End Sub ' New 14 15     ' property WeeklySalary 16     Public Property WeeklySalary() As Decimal 17        Get 18           Return weeklySalaryValue 19        End Get 20 21        Set(ByVal salary As Decimal) 22           If salary < 0.0 Then ' validate salary 23              weeklySalaryValue = 0 24           Else 25              weeklySalaryValue = salary 26           End If 27        End Set 28     End Property ' WeeklySalary 29 30     ' calculate earnings; override abstract method CalculateEarnings 31     Public Overrides Function CalculateEarnings() As Decimal         32        Return WeeklySalary                                           33     End Function ' CalculateEarnings                                 34 35     ' return String representation of SalariedEmployee object         36     Public Overrides Function ToString() As String                    37        Return ("salaried employee: " & MyBase.ToString() & vbCrLf & _ 38           String.Format("weekly salary {0:C}", WeeklySalary))         39     End Function ' ToString                                           40  End Class ' SalariedEmployee

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 HourlyEmployee

Class 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.

 1  ' Fig. 11.6: HourlyEmployee.vb  2  ' HourlyEmployee class inherits Employee.  3  Public Class HourlyEmployee  4     Inherits Employee         5  6     Private wageValue As Decimal ' wage per hour  7     Private hoursValue As Decimal ' hours worked for week  8  9     ' five-argument constructor 10     Public Sub New (ByVal first As String, ByVal last As String, _ 11        ByVal ssn As String, ByVal hourlyWage As Decimal, _ 12        ByVal hoursWorked As Decimal) 13        MyBase.New(first, last, ssn) ' pass to Employee constructor 14        Wage = hourlyWage ' validate and store hourly wage 15        Hours = hoursWorked ' validate and store hours worked 16     End Sub ' New 17 18     ' property Wage 19     Public Property Wage() As Decimal 20        Get 21           Return wageValue 22        End Get 23 24        Set(ByVal hourlyWage As Decimal) 25           If hourlyWage < 0 Then ' validate hourly wage 26              wageValue = 0 27           Else 28              wageValue = hourlyWage 29           End If 30        End Set 31     End Property ' Wage 32 33     ' property Hours 34     Public Property Hours() As Decimal 35        Get 36           Return hoursValue 37        End Get 38 39        Set(ByVal hoursWorked As Decimal) 40           If (hoursWorked >= 0.0) AndAlso (hoursWorked <= 168.0) Then 41              hoursValue = hoursWorked ' valid weekly hours 42           Else 43              hoursValue = 0 44           End If 45        End Set 46     End Property ' Hours 47 48     ' calculate earnings; override abstract method CalculageEarnings 49     Public Overrides Function CalculateEarnings() As Decimal         50        If Hours <= 40 Then ' no overtime                             51           Return Wage * Hours                                        52        Else                                                          53           Return 40 * Wage + ((Hours - 40) * Wage * 1.5D)            54        End If                                                        55     End Function ' CalculateEarnings                                 56 57     ' return String representation of HourlyEmployee object         58     Public Overrides Function ToString() As String                  59        Return ("hourly employee: " & MyBase.ToString() & vbCrLf & _ 60           String.Format("hourly wage: {0:C}; hours worked: {1}", _  61           Wage, Hours))                                             62     End Function ' ToString                                         63  End Class ' HourlyEmployee

11.5.4. Creating Concrete Derived Class CommissionEmployee

Class 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.

 1  ' Fig. 11.7: CommissionEmployee.vb  2  ' CommissionEmployee class inherits Employee.  3  Public Class CommissionEmployee  4     Inherits Employee             5  6     Private grossSalesValue As Decimal  7     Private commissionRateValue As Decimal  8  9     ' five-argument constructor 10     Public Sub New (ByVal first As String, ByVal last As String, _ 11        ByVal ssn As String, ByVal sales As Decimal, ByVal rate As Decimal) 12        MyBase.New(first, last, ssn) ' pass to Employee constructor 13        GrossSales = sales ' validate and store gross sales 14        CommissionRate = rate ' validate and store commission rate 15     End Sub ' New 16 17     ' property GrossSales 18     Public Property GrossSales() As Decimal 19        Get 20           Return grossSalesValue 21        End Get 22 23        Set(ByVal sales As Decimal) 24           If sales < 0 Then ' validate gross sales 25              grossSalesValue = 0 26           Else 27              grossSalesValue = sales 28           End If 29        End Set 30     End Property ' GrossSales 31 32     ' property CommissionRate 33     Public Property CommissionRate() As Decimal 34        Get 35           Return commissionRateValue 36        End Get 37 38        Set(ByVal rate As Decimal) 39           If rate > 0.0 AndAlso rate < 1.0 Then ' validate commission rate 40              commissionRateValue = rate 41           Else 42              commissionRateValue = 0 43           End If 44        End Set 45     End Property ' CommissionRate 46 47     ' calculate earnings; override abstract method CalculateEarnings 48     Public Overrides Function CalculateEarnings() As Decimal         49        Return CommissionRate * GrossSales                               50     End Function ' CalculateEarnings                                 51 52     ' return String representation of CommissionEmployee object         53     Public Overrides Function ToString() As String                      54        Return ("commission employee: " & MyBase.ToString() & vbCrLf & _ 55           String.Format("gross sales: {0:C}; commission rate: {1}", _   56           GrossSales, CommissionRate))                                  57     End Function ' ToString                                             58  End Class ' CommissionEmployee

11.5.5. Creating Indirect Concrete Derived Class BasePlusCommissionEmployee

Class 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.

 1  ' Fig. 11.8: BasePlusCommissionEmployee.vb  2  ' BasePlusCommissionEmployee class inherits CommissionEmployee.  3  Public Class BasePlusCommissionEmployee  4     Inherits CommissionEmployee  5  6     Private baseSalaryValue As Decimal ' base salary  7  8     ' six-argument constructor  9     Public Sub New(ByVal first As String, ByVal last As String, _ 10        ByVal ssn As String, ByVal sales As Decimal, _ 11        ByVal rate As Decimal, ByVal salary As Decimal) 12        MyBase.New(first, last, ssn, sales, rate) 13        BaseSalary = salary 14     End Sub ' New 15 16     ' property BaseSalary 17     Public Property BaseSalary() As Decimal 18        Get 19           Return baseSalaryValue 20        End Get 21 22        Set(ByVal salary As Decimal) 23           If salary < 0 Then ' validate salary 24              baseSalaryValue = 0 25           Else 26              baseSalaryValue = salary 27           End If 28        End Set 29     End Property ' BaseSalary 30 31     ' calculate earnings; override method CalculateEarnings 32     Public Overrides Function CalculateEarnings() As Decimal 33        Return BaseSalary + MyBase.CalculateEarnings() 34     End Function ' CalculateEarnings 35 36     ' return String representation of BasePlusCommissionEmployee object 37     Public Overrides Function ToString() As String 38        Return ("base-salaried " & MyBase.ToString() & _ 39           String.Format("; base salary: {0:C}", BaseSalary)) 40     End Function ' ToString 41  End Class ' BasePlusCommissionEmployee

11.5.6. Demonstrating Polymorphic Processing, Expression TypeOf...Is, TryCast and Downcasting

To 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.

 1  ' Fig. 11.9: PayrollSystemTest.vb  2  ' Employee hierarchy test program.  3  Module PayrollSystemTest  4     Sub Main()  5        ' create derived class objects                                       6        Dim salariedEmployee As New SalariedEmployee( _                      7           "John", "Smith", "111-11-1111", 800)                              8        Dim hourlyEmployee As New HourlyEmployee( _                          9           "Karen", "Price", "222-22-2222", 16.75D, 40)                      10        Dim commissionEmployee As New CommissionEmployee( _                 11           "Sue", "Jones", "333-33-3333", 10000, 0.06D)                     12        Dim basePlusCommissionEmployee As New BasePlusCommissionEmployee( _ 13           "Bob", "Lewis", "444-44-4444", 5000, 0.04D, 300)                 14 15        ' display each employee's info non-polymorphically 16        Console.WriteLine("Employees processed individually:" & vbCrLf) 17        Console.WriteLine(salariedEmployee.ToString() & vbCrLf & _ 18           String.Format("earned: {0:C}", _ 19           salariedEmployee.CalculateEarnings()) & vbCrLf) 20        Console.WriteLine(hourlyEmployee.ToString() & vbCrLf & _ 21           String.Format("earned: {0:C}", _ 22           hourlyEmployee.CalculateEarnings()) & vbCrLf) 23        Console.WriteLine(commissionEmployee.ToString() & vbCrLf & _ 24           String.Format("earned: {0:C}", _ 25           commissionEmployee.CalculateEarnings()) & vbCrLf) 26        Console.WriteLine(basePlusCommissionEmployee.ToString() & _ 27           vbCrLf & String.Format("earned: {0:C}", _ 28           basePlusCommissionEmployee.CalculateEarnings()) & vbCrLf) 29 30        ' create four-element Employee array                               31        Dim employees() As Employee = {salariedEmployee, hourlyEmployee, _ 32           commissionEmployee, basePlusCommissionEmployee}                 33 34        Console.WriteLine("Employees processed polymorphically:" & vbCrLf) 35 36        ' polymorphically process each element in array employees 37        For Each currentEmployee As Employee In employees 38           Console.WriteLine(currentEmployee.ToString()) 39 40           ' determine if currentEmployee is a BasePlusCommissionEmployee 41           If (TypeOf currentEmployee Is BasePlusCommissionEmployee) Then 42 43              ' downcast Employee reference to BasePlusCommissionEmployee 44              Dim employee As BasePlusCommissionEmployee = _ 45                 TryCast(currentEmployee, BasePlusCommissionEmployee) 46 47              employee.BaseSalary *= 1.1D 48              Console.WriteLine(String.Format( _ 49                 "new base salary with 10% increase is: {0:C}", _ 50                 employee.BaseSalary)) 51        End If 52 53              Console.Write(String.Format("earned {0:C}", _ 54                 currentEmployee.CalculateEarnings()) & vbCrLf & vbCrLf) 55        Next 56 57        ' get type name of each object in employees array             58        For i As Integer = 0 To employees.Length - 1                  59           Console.WriteLine(String.Format("Employee {0} is a {1}", _ 60              i, employees(i).GetType().FullName))                    61        Next                                                          62     End Sub ' Main 63  End Module ' PayrollSystemTest

[View full width]

Employees processed individually: salaried employee: John Smith social security number: 111-11-1111 weekly salary $800.00 earned: $800.00 hourly employee: Karen Price social security number: 222-22-2222 hourly wage: $16.75; hours worked: 40 earned: $670.00 commission employee: Sue Jones social security number: 333-33-3333 gross sales: $10,000.00; commission rate: 0.06 earned: $600.00 base-salaried commission employee: Bob Lewis social security number: 444-44-4444 gross sales: $5,000.00; commission rate: 0.04; base salary: $300.00 earned: $500.00 Employees processed polymorphically: salaried employee: John Smith social security number: 111-11-1111 weekly salary $800.00 earned $800.00 hourly employee: Karen Price social security number: 222-22-2222 hourly wage: $16.75; hours worked: 40 earned $670.00 commission employee: Sue Jones social security number: 333-33-3333 gross sales: $10,000.00; commission rate: 0.06 earned $600.00 base-salaried commission employee: Bob Lewis social security number: 444-44-4444 gross sales: $5,000.00; commission rate: 0.04; base salary: $300.00 new base salary with 10% increase is: $330.00 earned $530.00 Employee 0 is a PayrollSystem.SalariedEmployee Employee 1 is a PayrollSystem.HourlyEmployee Employee 2 is a PayrollSystem.CommissionEmployee Employee 3 is a PayrollSystem .BasePlusCommissionEmployee



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 Type

We 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 Type

Lines 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

Assigning a base class variable to a derived class variable (without an explicit cast) is a compilation error.


Error-Prevention Tip 11.1

If at execution time the reference of a derived class object has been assigned to a variable of one of its direct or indirect base classes, it is acceptable to cast the reference stored in that base class variable back to a reference of the derived class type. Before performing such a cast, use the Typeof...Is expression to ensure that the object is indeed an object of an appropriate derived class type. You can then use TRyCast to downcast from the base class type to the derived class type.


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 Downcasting

We 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 Variables

Now 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:

  1. Assigning a base class reference to a base class variable is straightforward.

  2. Assigning a derived class reference to a derived class variable is straightforward.

  3. Assigning a derived class reference to a base class variable is safe, because the derived class object is an object of its base class. However, this reference can be used to refer only to base class members. Referring to a derived-class-only member through the base class variable is a compilation error.

  4. Attempting to assign a base class reference to a derived class variable is a compilation error (when Option Strict is On). To avoid this error, the base class reference must be cast to a derived class type explicitly. At execution time, if the object to which the reference refers is not a derived class object, an exception will occur. (For more on exception handling, see Chapter 12, Exception Handling.) The TypeOf...Is expression can be used to ensure that such a cast is performed only if the object is a derived class object. Then a tryCast can be used to perform the downcast operation.



Visual BasicR 2005 for Programmers. DeitelR Developer Series
Visual Basic 2005 for Programmers (2nd Edition)
ISBN: 013225140X
EAN: 2147483647
Year: 2004
Pages: 435

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net