2. When the app executes, another compiler (known as the just-in-time compiler
12.5 Case Study: Payroll System Using Polymorphism
This section reexamines theCommissionEmployee-BasePlusCommissionEmployeehierar- chy that we explored in Section 11.4. Now we use an abstract method and polymorphism to perform payroll calculations based on the type of employee. We create an enhanced em- ployee hierarchy to solve the following problem:
A company pays its employees on a weekly basis. The employees are of four types: Sala- ried employees are paid a fixed weekly salary regardless of the number of hours worked, hourly employees are paid by the hour and receive "time-and-a-half" overtime pay for all hours worked in excess of 40 hours, commission employees are paid a percentage of their sales, and salaried-commission employees receive a base salary plus a percentage of their sales. For the current pay period, the company has decided to reward salaried- commission employees by adding 10% to their base salaries. The company wants to implement an app that performs its payroll calculations polymorphically.
Common Programming Error 12.2
Failure to implement a base class’s abstract methods and properties in a derived class is a compilation error unless the derived class is also declaredabstract.
12.5 Case Study: Payroll System Using Polymorphism 451
We useabstractclassEmployeeto represent the general concept of an employee. The classes that extendEmployeeareSalariedEmployee,CommissionEmployeeandHourly-
Employee. ClassBasePlusCommissionEmployee—which extendsCommissionEmployee— represents the last employee type. The UML class diagram in Fig. 12.2 shows the inheri- tance hierarchy for our polymorphic employee payroll app. Abstract classEmployeeisital- icized, as per the convention of the UML.
Abstract base classEmployeedeclares the “interface” to the hierarchy—that is, the set of members that an app can invoke on allEmployeeobjects. We use the term “interface”
here in a general sense to refer to the various ways apps can communicate with objects of anyEmployeederived class. Be careful not to confuse the general notion of an “interface”
with the formal notion of a C# interface, the subject of Section 12.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 those pieces of data appear in abstract base classEmployee.
The following subsections implement theEmployeeclass hierarchy. Section 12.5.1 implementsabstractbase classEmployee. Sections 12.5.2–12.5.5 each implement one of the concrete classes. Section 12.5.6 implements a test app that builds objects of all these classes and processes those objects polymorphically.
12.5.1 Creating Abstract Base ClassEmployee
ClassEmployee(Fig. 12.4) provides methodsEarningsandToString, in addition to the auto-implemented properties that manipulateEmployee’s data. AnEarningsmethod cer- tainly applies generically to all employees. But each earnings calculation depends on the employee’s class. So we declareEarningsasabstractin base classEmployee, because a default implementation does not make sense for that method—there’s not enough infor- mation to determine what amountEarningsshould return. Each derived class overrides
Earningswith a specific implementation. To calculate an employee’s earnings, the app as- signs a reference to the employee’s object to a base classEmployeevariable, then invokes theEarningsmethod on that variable. We maintain an array ofEmployeevariables, each of which holds a reference to anEmployeeobject (of course, therecannotbeEmployeeob- jects becauseEmployeeis anabstractclass—because of inheritance, however, all objects of all derived classes ofEmployeemay nevertheless be thought of asEmployeeobjects). The app iterates through the array and calls methodEarningsfor eachEmployeeobject. These Fig. 12.2 | Employeehierarchy UML class diagram.
Employee
CommissionEmployee HourlyEmployee SalariedEmployee
BasePlusCommissionEmployee
method calls are processed polymorphically. IncludingEarningsas an abstract method in
Employeeforcesevery directly derivedconcreteclass ofEmployeeto overrideEarningswith a method that performs an appropriate pay calculation.
MethodToStringin classEmployeereturns astringcontaining the employee’s first name, last name and social security number. Each derived class ofEmployee overrides methodToStringto create a string representation of an object of that class containing the employee’s type (e.g., "salaried employee:"), followed by the rest of the employee’s information.
The diagram in Fig. 12.3 shows each of the five classes in the hierarchy down the left side and methodsEarnings andToString across the top. For each class, the diagram shows the desired results of each method. [Note:We do not list base classEmployee’s prop- erties because they’re not overridden in any of the derived classes—each of these properties is inherited and used “as is” by each of the derived classes.]
ClassEmployee
Let’s consider classEmployee’s declaration (Fig. 12.4). The class includes a constructor that takes the first name, last name and social security number as arguments (lines 15–20);
properties with publicgetaccessors for obtaining the first name, last name and social se- curity number (lines 6, 9 and 12, respectively); methodToString(lines 23–27), which uses properties to return the string representation of theEmployee; andabstractmethod Fig. 12.3 | Polymorphic interface for theEmployeehierarchy classes.
weeklySalary abstract
Commission- Employee
BasePlus- Commission- Employee Hourly- Employee Salaried- Employee Employee
ToString Earnings
If hours <= 40 wage * hours If hours > 40
40 * wage + ( hours - 40 ) * wage * 1.5 commissionRate * grossSales
( commissionRate * grossSales ) + baseSalary
salaried employee: firstName lastName social security number: SSN weekly salary: weeklysalary hourly employee: firstName lastName social security number: SSN hourly wage: wage
hours worked: hours
commission employee: firstName lastName social security number: SSN
gross sales: grossSales
commission rate: commissionRate base salaried commission employee:
firstName lastName
social security number: SSN gross sales: grossSales
commission rate: commissionRate base salary: baseSalary firstName lastName
social security number: SSN
12.5 Case Study: Payroll System Using Polymorphism 453
Earnings(line 30), whichmustbe implemented byconcretederived classes. TheEmployee constructor does not validate the social security number in this example. Normally, such validation should be provided.
Why did we declareEarningsas anabstractmethod? As explained earlier, it simply does not make sense to provide an implementation of this method in classEmployee. We cannot calculate the earnings for a general Employee—we first must know the specific
Employee type to determine the appropriate earnings calculation. By declaring this methodabstract, we indicate that eachconcretederived classmustprovide an appropriate
Earningsimplementation and that an app will be able to use base-classEmployeevariables to invoke methodEarningspolymorphically foranytype ofEmployee.
12.5.2 Creating Concrete Derived ClassSalariedEmployee
ClassSalariedEmployee(Fig. 12.5) extends classEmployee(line 5) and overridesEarn-
ings(lines 34–37), which makesSalariedEmployeea concrete class. The class includes a constructor (lines 10–14) that takes a first name, a last name, a social security number and a weekly salary as arguments; propertyWeeklySalary(lines 17–31) to manipulate instance 1 // Fig. 12.4: Employee.cs
2 // Employee abstract base class.
3 4 {
5 // read-only property that gets employee's first name 6 public string FirstName { get; private set; }
7
8 // read-only property that gets employee's last name 9 public string LastName { get; private set; }
10
11 // read-only property that gets employee's social security number 12 public string SocialSecurityNumber { get; private set; }
13
14 // three-parameter constructor
15 public Employee( string first, string last, string ssn )
16 {
17 FirstName = first;
18 LastName = last;
19 SocialSecurityNumber = ssn;
20 } // end three-parameter Employee constructor 21
22 // return string representation of Employee object, using properties 23 public override string ToString()
24 {
25 return string.Format( "{0} {1}\nsocial security number: {2}", 26 FirstName, LastName, SocialSecurityNumber );
27 } // end method ToString 28
29 // abstract method overridden by derived classes 30
31 } // end abstract class Employee
Fig. 12.4 | Employeeabstract base class.
public abstract class Employee
public abstract decimal Earnings(); // no implementation here
variableweeklySalary, including asetaccessor that ensures we assign only nonnegative values toweeklySalary; methodEarnings(lines 34–37) to calculate aSalariedEmploy- ee’s earnings; and methodToString(lines 40–44), which returns astringincluding the employee’s type, namely,"salaried employee: ", followed by employee-specific infor- mation produced by base class Employee’s ToString method andSalariedEmployee’s
WeeklySalaryproperty. ClassSalariedEmployee’s constructor passes the first name, last name and social security number to theEmployeeconstructor (line 11) via a constructor initializer to initialize the base class’s data. MethodEarningsoverridesEmployee’s abstract methodEarnings to provide a concrete implementation that returns theSalariedEm- ployee’s weekly salary. If we do not implementEarnings, classSalariedEmployeemust be declaredabstract—otherwise, a compilation error occurs (and, of course, we want
SalariedEmployeeto be a concrete class).
1 // Fig. 12.5: SalariedEmployee.cs
2 // SalariedEmployee class that extends Employee.
3 using System;
4 5 6 {
7 private decimal weeklySalary;
8
9 // four-parameter constructor
10 public SalariedEmployee( string first, string last, string ssn, 11 decimal salary ) : base( first, last, ssn )
12 {
13 WeeklySalary = salary; // validate salary via property 14 } // end four-parameter SalariedEmployee constructor 15
16 // property that gets and sets salaried employee's salary 17 public decimal WeeklySalary
18 {
19 get
20 {
21 return weeklySalary;
22 } // end get
23 set
24 {
25 if ( value >= 0 ) // validation
26 weeklySalary = value;
27 else
28 throw new ArgumentOutOfRangeException( "WeeklySalary", 29 value, "WeeklySalary must be >= 0" );
30 } // end set
31 } // end property WeeklySalary 32
33 // calculate earnings; override abstract method Earnings in Employee 34
35 36 37
Fig. 12.5 | SalariedEmployeeclass that extendsEmployee. (Part 1 of 2.)
public class SalariedEmployee : Employee
public override decimal Earnings() {
return WeeklySalary;
} // end method Earnings
12.5 Case Study: Payroll System Using Polymorphism 455
SalariedEmployeemethodToString(lines 40–44) overridesEmployee’s version. If classSalariedEmployeedidnotoverrideToString,SalariedEmployeewould have inher- ited the Employeeversion. In that case, SalariedEmployee’s ToStringmethod would simply return the employee’s full name and social security number, which does not ade- quately represent aSalariedEmployee. To produce a complete string representation of a
SalariedEmployee, the derived class’sToStringmethod returns"salaried employee: ", followed by the base-classEmployee-specific information (i.e., first name, last name and social security number) obtained by invoking the base class’sToString(line 43)—this is a nice example of code reuse. Thestringrepresentation of aSalariedEmployeealso con- tains the employee’s weekly salary, obtained by using the class’sWeeklySalaryproperty.
12.5.3 Creating Concrete Derived ClassHourlyEmployee
ClassHourlyEmployee(Fig. 12.6) also extends classEmployee(line 5). The class includes a constructor (lines 11–17) that takes as arguments a first name, a last name, a social secu- rity number, an hourly wage and the number of hours worked. Lines 20–34 and 37–51 declare propertiesWageandHoursfor instance variableswageandhours, respectively. The
setaccessor in propertyWageensures thatwageis nonnegative, and the setaccessor in propertyHoursensures thathoursis in the range0–168(the total number of hours in a week) inclusive. The class overrides methodEarnings(lines 54–60) to calculate anHour-
lyEmployee’s earnings and methodToString(lines 63–68) to return the employee’s string representation. TheHourlyEmployeeconstructor, similarly to theSalariedEmployeecon- structor, passes the first name, last name and social security number to the base-classEm-
ployeeconstructor (line 13) to initialize the base class’sdata. Also, methodToStringcalls base-class methodToString(line 67) to obtain theEmployee-specific information (i.e., first name, last name and social security number).
38
39 // return string representation of SalariedEmployee object 40
41 42 43 44
45 } // end class SalariedEmployee
1 // Fig. 12.6: HourlyEmployee.cs
2 // HourlyEmployee class that extends Employee.
3 using System;
4 5 6 {
7 private decimal wage; // wage per hour
8 private decimal hours; // hours worked for the week 9
Fig. 12.6 | HourlyEmployeeclass that extendsEmployee. (Part 1 of 3.) Fig. 12.5 | SalariedEmployeeclass that extendsEmployee. (Part 2 of 2.)
public override string ToString() {
return string.Format( "salaried employee: {0}\n{1}: {2:C}", base.ToString(), "weekly salary", WeeklySalary );
} // end method ToString
public class HourlyEmployee : Employee
10 // five-parameter constructor
11 public HourlyEmployee( string first, string last, string ssn, 12 decimal hourlyWage, decimal hoursWorked )
13 : base( first, last, ssn )
14 {
15 Wage = hourlyWage; // validate hourly wage via property 16 Hours = hoursWorked; // validate hours worked via property 17 } // end five-parameter HourlyEmployee constructor
18
19 // property that gets and sets hourly employee's wage 20 public decimal Wage
21 {
22 get
23 {
24 return wage;
25 } // end get
26 set
27 {
28 if ( value >= 0 ) // validation
29 wage = value;
30 else
31 throw new ArgumentOutOfRangeException( "Wage", 32 value, "Wage must be >= 0" );
33 } // end set 34 } // end property Wage 35
36 // property that gets and sets hourly employee's hours 37 public decimal Hours
38 {
39 get
40 {
41 return hours;
42 } // end get
43 set
44 {
45 if ( value >= 0 && value <= 168 ) // validation
46 hours = value;
47 else
48 throw new ArgumentOutOfRangeException( "Hours", 49 value, "Hours must be >= 0 and <= 168" );
50 } // end set
51 } // end property Hours 52
53 // calculate earnings; override Employee’s abstract method Earnings 54
55 56 57 58 59 60 61
Fig. 12.6 | HourlyEmployeeclass that extendsEmployee. (Part 2 of 3.)
public override decimal Earnings() {
if ( Hours <= 40 ) // no overtime return Wage * Hours;
else
return ( 40 * Wage ) + ( ( Hours - 40 ) * Wage * 1.5M );
} // end method Earnings
12.5 Case Study: Payroll System Using Polymorphism 457
12.5.4 Creating Concrete Derived ClassCommissionEmployee
ClassCommissionEmployee(Fig. 12.7) extends classEmployee(line 5). The class includes a constructor (lines 11–16) that takes a first name, a last name, a social security number, a sales amount and a commission rate; properties (lines 19–33 and 36–50) for instance variablesgrossSalesandcommissionRate, respectively; methodEarnings(lines 53–56) to calculate aCommissionEmployee’s earnings; and methodToString(lines 59–64), which returns the employee’s string representation. TheCommissionEmployee’s constructor also passes the first name, last name and social security number to theEmployeeconstructor (line 12) to initializeEmployee’sdata. MethodToStringcalls base-class methodToString (line 62) to obtain theEmployee-specific information (i.e., first name, last name and social security number).
62 // return string representation of HourlyEmployee object 63
64 65 66 67 68
69 } // end class HourlyEmployee
1 // Fig. 12.7: CommissionEmployee.cs
2 // CommissionEmployee class that extends Employee.
3 using System;
4 5 6 {
7 private decimal grossSales; // gross weekly sales 8 private decimal commissionRate; // commission percentage 9
10 // five-parameter constructor
11 public CommissionEmployee( string first, string last, string ssn, 12 decimal sales, decimal rate ) : base( first, last, ssn )
13 {
14 GrossSales = sales; // validate gross sales via property 15 CommissionRate = rate; // validate commission rate via property 16 } // end five-parameter CommissionEmployee constructor
17
18 // property that gets and sets commission employee's gross sales 19 public decimal GrossSales
20 {
21 get
22 {
23 return grossSales;
24 } // end get
25 set
26 {
Fig. 12.7 | CommissionEmployeeclass that extendsEmployee. (Part 1 of 2.) Fig. 12.6 | HourlyEmployeeclass that extendsEmployee. (Part 3 of 3.)
public override string ToString() {
return string.Format(
"hourly employee: {0}\n{1}: {2:C}; {3}: {4:F2}",
base.ToString(), "hourly wage", Wage, "hours worked", Hours );
} // end method ToString
public class CommissionEmployee : Employee
12.5.5 Creating Indirect Concrete Derived Class BasePlusCommissionEmployee
ClassBasePlusCommissionEmployee(Fig. 12.8) extends classCommissionEmployee(line 5) and therefore is anindirectderived class of classEmployee. ClassBasePlusCommission- Employeehas a constructor (lines 10–15) 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
CommissionEmployeeconstructor (line 12) to initialize the base class’s data.BasePlusCom- missionEmployeealso contains propertyBaseSalary(lines 19–33) to manipulate instance 27 if ( value >= 0 )
28 grossSales = value;
29 else
30 throw new ArgumentOutOfRangeException(
31 "GrossSales", value, "GrossSales must be >= 0" );
32 } // end set
33 } // end property GrossSales 34
35 // property that gets and sets commission employee's commission rate 36 public decimal CommissionRate
37 {
38 get
39 {
40 return commissionRate;
41 } // end get
42 set
43 {
44 if ( value > 0 && value < 1 ) 45 commissionRate = value;
46 else
47 throw new ArgumentOutOfRangeException( "CommissionRate", 48 value, "CommissionRate must be > 0 and < 1" );
49 } // end set
50 } // end property CommissionRate 51
52 // calculate earnings; override abstract method Earnings in Employee 53
54 55 56 57
58 // return string representation of CommissionEmployee object 59
60 61 62 63 64
65 } // end class CommissionEmployee
Fig. 12.7 | CommissionEmployeeclass that extendsEmployee. (Part 2 of 2.)
public override decimal Earnings() {
return CommissionRate * GrossSales;
} // end method Earnings
public override string ToString() {
return string.Format( "{0}: {1}\n{2}: {3:C}\n{4}: {5:F2}",
"commission employee", base.ToString(),
"gross sales", GrossSales, "commission rate", CommissionRate );
} // end method ToString
12.5 Case Study: Payroll System Using Polymorphism 459
variablebaseSalary. MethodEarnings(lines 36–39) calculates aBasePlusCommission- Employee’s earnings. Line 38 in methodEarningscalls base classCommissionEmployee’s
Earningsmethod to calculate the commission-based portion of the employee’s earnings.
Again, this shows the benefits of code reuse. BasePlusCommissionEmployee’sToString method (lines 42–46) creates a string representation of aBasePlusCommissionEmployee
that contains"base-salaried", followed by thestringobtained by invoking base class
CommissionEmployee’sToStringmethod (another example of code reuse), then the base salary. The result is astringbeginning with"base-salaried commission employee", fol- lowed by the rest of theBasePlusCommissionEmployee’s information. Recall thatCommis-
sionEmployee’sToStringmethod obtains the employee’s first name, last name and social security number by invoking theToStringmethod ofitsbase class (i.e.,Employee)—a fur- ther demonstration of code reuse.BasePlusCommissionEmployee’s ToString initiates a chain of method callsthat spans all three levels of theEmployeehierarchy.
1 // Fig. 12.8: BasePlusCommissionEmployee.cs
2 // BasePlusCommissionEmployee class that extends CommissionEmployee.
3 using System;
4 5 6 {
7 private decimal baseSalary; // base salary per week 8
9 // six-parameter constructor
10 public BasePlusCommissionEmployee( string first, string last, 11 string ssn, decimal sales, decimal rate, decimal salary ) 12 : base( first, last, ssn, sales, rate )
13 {
14 BaseSalary = salary; // validate base salary via property 15 } // end six-parameter BasePlusCommissionEmployee constructor 16
17 // property that gets and sets
18 // base-salaried commission employee's base salary 19 public decimal BaseSalary
20 {
21 get
22 {
23 return baseSalary;
24 } // end get
25 set
26 {
27 if ( value >= 0 )
28 baseSalary = value;
29 else
30 throw new ArgumentOutOfRangeException( "BaseSalary", 31 value, "BaseSalary must be >= 0" );
32 } // end set
33 } // end property BaseSalary 34
Fig. 12.8 | BasePlusCommissionEmployeeclass that extendsCommissionEmployee. (Part 1 of 2.)
public class BasePlusCommissionEmployee : CommissionEmployee
12.5.6 Polymorphic Processing, Operatorisand Downcasting
To test ourEmployeehierarchy, the app in Fig. 12.9 creates an object of each of the four concrete classes SalariedEmployee, HourlyEmployee, CommissionEmployee and Base-
PlusCommissionEmployee. The app manipulates these objects, first via variables of each object’s own type, then polymorphically, using an array ofEmployeevariables. While pro- cessing the objects polymorphically, the app increases the base salary of each Base-
PlusCommissionEmployeeby 10% (this, of course, requires determining the object’s type atexecutiontime). Finally, the app polymorphically determines and outputs the type of each object in theEmployeearray. Lines 10–20 create objects of each of the four concrete
Employeederived classes. Lines 24–32 output the string representation and earnings of each of these objects. Each object’sToStringmethod is calledimplicitlybyWriteLine when the object is output as astringwith format items.
Assigning Derived-Class Objects to Base-Class References
Line 35 declaresemployeesand assigns it an array of fourEmployeevariables. Lines 38–
41 assign aSalariedEmployeeobject, anHourlyEmployeeobject, aCommissionEmployee
object and a BasePlusCommissionEmployee object to employees[0], employees[1],
employees[2] and employees[3], respectively. Each assignment is allowed, because a
SalariedEmployeeis anEmployee, anHourlyEmployeeis anEmployee, a Commission- Employeeis anEmployeeand aBasePlusCommissionEmployeeis anEmployee. Therefore, we can assign the references ofSalariedEmployee,HourlyEmployee,CommissionEmploy- ee and BasePlusCommissionEmployee objects to base-class Employee variables, even thoughEmployeeis anabstractclass.
35 // calculate earnings; override method Earnings in CommissionEmployee 36
37 38 39 40
41 // return string representation of BasePlusCommissionEmployee object 42
43 44 45 46
47 } // end class BasePlusCommissionEmployee
1 // Fig. 12.9: PayrollSystemTest.cs 2 // Employee hierarchy test app.
3 using System;
4
5 public class PayrollSystemTest 6 {
Fig. 12.9 | Employeehierarchy test app. (Part 1 of 4.)
Fig. 12.8 | BasePlusCommissionEmployeeclass that extendsCommissionEmployee. (Part 2 of 2.)
public override decimal Earnings() {
return BaseSalary + base.Earnings();
} // end method Earnings
public override string ToString() {
return string.Format( "base-salaried {0}; base salary: {1:C}", base.ToString(), BaseSalary );
} // end method ToString
12.5 Case Study: Payroll System Using Polymorphism 461
7 public static void Main( string[] args )
8 {
9 // create derived-class objects 10
11 12 13 14 15 16 17 18 19 20 21
22 Console.WriteLine( "Employees processed individually:\n" );
23
24 Console.WriteLine( "{0}\nearned: {1:C}\n",
25 salariedEmployee, salariedEmployee.Earnings() );
26 Console.WriteLine( "{0}\nearned: {1:C}\n", 27 hourlyEmployee, hourlyEmployee.Earnings() );
28 Console.WriteLine( "{0}\nearned: {1:C}\n",
29 commissionEmployee, commissionEmployee.Earnings() );
30 Console.WriteLine( "{0}\nearned: {1:C}\n", 31 basePlusCommissionEmployee,
32 basePlusCommissionEmployee.Earnings() );
33
34 // create four-element Employee array 35
36
37 // initialize array with Employees of derived types 38
39 40 41 42
43 Console.WriteLine( "Employees processed polymorphically:\n" );
44
45 // generically process each element in array employees 46 foreach ( Employee currentEmployee in employees )
47 {
48 Console.WriteLine( currentEmployee ); // invokes ToString 49
50 // determine whether element is a BasePlusCommissionEmployee 51
52 {
53 // downcast Employee reference to 54 // BasePlusCommissionEmployee reference 55 BasePlusCommissionEmployee employee =
56 ;
57
58 employee.BaseSalary *= 1.10M;
Fig. 12.9 | Employeehierarchy test app. (Part 2 of 4.)
SalariedEmployee salariedEmployee =
new SalariedEmployee( "John", "Smith", "111-11-1111", 800.00M );
HourlyEmployee hourlyEmployee =
new HourlyEmployee( "Karen", "Price",
"222-22-2222", 16.75M, 40.0M );
CommissionEmployee commissionEmployee = new CommissionEmployee( "Sue", "Jones",
"333-33-3333", 10000.00M, .06M );
BasePlusCommissionEmployee basePlusCommissionEmployee = new BasePlusCommissionEmployee( "Bob", "Lewis",
"444-44-4444", 5000.00M, .04M, 300.00M );
Employee[] employees = new Employee[ 4 ];
employees[ 0 ] = salariedEmployee;
employees[ 1 ] = hourlyEmployee;
employees[ 2 ] = commissionEmployee;
employees[ 3 ] = basePlusCommissionEmployee;
if ( currentEmployee is BasePlusCommissionEmployee )
( BasePlusCommissionEmployee ) currentEmployee