2. When the app executes, another compiler (known as the just-in-time compiler
11.4 Relationship between Base Classes and Derived Classes
In this section, we use an inheritance hierarchy containing types of employees in a com- pany’s payroll app to discuss the relationship between a base class and its derived classes.
In this company,commission employees(who will be represented as objects of a base class) are paid a percentage of their sales, whilebase-salaried commission employees(who will be represented as objects of a derived class) receive a base salaryplusa percentage of their sales.
We divide our discussion of the relationship between commission employees and base-salaried commission employees into five examples:
1. The first creates classCommissionEmployee, which directly inherits from classob- jectand declares asprivateinstance variables a first name, last name, social se- curity number, commission rate and gross (i.e., total) sales amount.
2. The second declares classBasePlusCommissionEmployee, which also directly in- herits from objectand declares asprivate instance variables a first name, last name, social security number, commission rate, gross sales amountandbase salary.
We create the class by writingeveryline of code the class requires—we’ll soon see that it’s more efficient to create this class by inheriting fromCommissionEmployee. 3. The third declares a separateBasePlusCommissionEmployeeclass that extends class
CommissionEmployee (i.e., a BasePlusCommissionEmployee is a Commission- Employeewho also has a base salary). We show that base-class methods must be ex- plicitly declaredvirtualif they’re to be overridden by methods in derived classes.
BasePlusCommissionEmployee attempts to access class CommissionEmployee’s
privatemembers, but this results in compilation errors because a derived classcan- notaccess its base class’sprivateinstance variables.
4. The fourth shows that if base classCommissionEmployee’s instance variables are declared asprotected, aBasePlusCommissionEmployeeclass that inherits from classCommissionEmployeecan access that data directly. For this purpose, we de- clare classCommissionEmployeewithprotectedinstance variables.
5. The fifth example demonstratesbest practiceby setting theCommissionEmployee
instance variables back toprivatein classCommissionEmployeeto enforce good software engineering. Then we show how a separate BasePlusCommissionEm- ployeeclass, which inherits from classCommissionEmployee, can useCommis-
sionEmployee’spublicmethods to manipulateCommissionEmployee’sprivate instance variables.
11.4.1 Creating and Using aCommissionEmployeeClass
We begin by declaring classCommissionEmployee(Fig. 11.4). Line 5 begins the class decla- ration. The colon (:) followed by class nameobjectat the end of the declaration header in- dicates that class CommissionEmployee extends (i.e., inherits from) class object (System.Objectin the Framework Class Library). C# programmers use inheritance to cre- ate classes from existing classes. In fact,everyclass in C# (exceptobject) extends an existing class. Because classCommissionEmployeeextends classobject, classCommissionEmployee
inherits the methods of classobject—classobjecthas no fields. Every C# class directly or indirectly inheritsobject’s methods. If a class does not specify that it inherits from another
11.4 Relationship between Base Classes and Derived Classes 411
class, the new classimplicitlyinherits fromobject. For this reason, you typically donotin- clude “: object” in your code—we do so in this example for demonstration purposes.
1 // Fig. 11.4: CommissionEmployee.cs
2 // CommissionEmployee class represents a commission employee.
3 using System;
4
5 public class CommissionEmployee : object 6 {
7 8 9 10 11 12
13 // five-parameter constructor 14
15 16 17 18 19 20 21 22 23 24
25 // read-only property that gets commission employee's first name 26 public string FirstName
27 {
28 get
29 {
30 return firstName;
31 } // end get
32 } // end property FirstName 33
34 // read-only property that gets commission employee's last name 35 public string LastName
36 {
37 get
38 {
39 return lastName;
40 } // end get
41 } // end property LastName 42
43 // read-only property that gets
44 // commission employee's social security number 45 public string SocialSecurityNumber
46 {
47 get
48 {
49 return socialSecurityNumber;
Fig. 11.4 | CommissionEmployeeclass represents a commission employee. (Part 1 of 2.)
private string firstName;
private string lastName;
private string socialSecurityNumber;
private decimal grossSales; // gross weekly sales private decimal commissionRate; // commission percentage
public CommissionEmployee( string first, string last, string ssn, decimal sales, decimal rate )
{
// implicit call to object constructor occurs here firstName = first;
lastName = last;
socialSecurityNumber = ssn;
GrossSales = sales; // validate gross sales via property CommissionRate = rate; // validate commission rate via property } // end five-parameter CommissionEmployee constructor
50 } // end get
51 } // end property SocialSecurityNumber 52
53 // property that gets and sets commission employee's gross sales 54 public decimal GrossSales
55 {
56 get
57 {
58 return grossSales;
59 } // end get
60 set
61 {
62 if ( value >= 0 )
63 grossSales = value;
64 else
65 throw new ArgumentOutOfRangeException(
66 "GrossSales", value, "GrossSales must be >= 0" );
67 } // end set
68 } // end property GrossSales 69
70 // property that gets and sets commission employee's commission rate 71 public decimal CommissionRate
72 {
73 get
74 {
75 return commissionRate;
76 } // end get
77 set
78 {
79 if ( value > 0 && value < 1 ) 80 commissionRate = value;
81 else
82 throw new ArgumentOutOfRangeException( "CommissionRate", 83 value, "CommissionRate must be > 0 and < 1" );
84 } // end set
85 } // end property CommissionRate 86
87 88 89 90 91 92 93 94 95 96 97 98 99 100 101
102 } // end class CommissionEmployee
Fig. 11.4 | CommissionEmployeeclass represents a commission employee. (Part 2 of 2.)
// calculate commission employee's pay public decimal Earnings()
{
return commissionRate * grossSales;
} // end method Earnings
// return string representation of CommissionEmployee object public override string ToString()
{
return string.Format(
"{0}: {1} {2}\n{3}: {4}\n{5}: {6:C}\n{7}: {8:F2}",
"commission employee", FirstName, LastName,
"social security number", SocialSecurityNumber,
"gross sales", GrossSales, "commission rate", CommissionRate );
} // end method ToString
11.4 Relationship between Base Classes and Derived Classes 413
CommissionEmployeeClass Overview
CommissionEmployee’s public services include a constructor (lines 14–23), methods
Earnings(lines 88–91) andToString(lines 94–101), and thepublicproperties (lines 26–85) for manipulating the class’s instance variablesfirstName,lastName,socialSecu- rityNumber,grossSales andcommissionRate (declared in lines 7–11). Each of its in- stance variable isprivate, so objects of other classes cannot directly access these variables.
Declaring instance variables asprivateand providingpublicproperties to manipulate and validate them helps enforce good software engineering. Thesetaccessors of proper- tiesGrossSalesandCommissionRate, for example,validatetheir arguments before assign- ing the values to instance variablesgrossSalesandcommissionRate, respectively.
CommissionEmployeeConstructor
Constructors arenotinherited, so classCommissionEmployeedoes not inherit classobject’s constructor. However, classCommissionEmployee’s constructor calls classobject’s construc- torimplicitly. In fact, before executing the code in its own body, the derived class’s construc- tor calls its direct base class’s constructor, either explicitly or implicitly (if no constructor call is specified), to ensure that the instance variables inherited from the base class are initialized properly. The syntax for calling a base-class constructor explicitly is discussed in Section 11.4.3. If the code does not include anexplicitcall to the base-class constructor, the compiler generates animplicitcall to the base class’sdefaultorparameterlessconstructor. The comment in line 17 indicates where the implicit call to the base classobject’s default con- structor is made (you do not write the code for this call). Classobject’s default (empty) con- structor does nothing. Even if a class does not have constructors, the default constructor that the compiler implicitly declares for the class will call the base class’s default or parameterless constructor. Classobjectis theonlyclass that does not have a base class.
After the implicit call toobject’s constructor occurs, lines 18–22 in the constructor assign values to the class’s instance variables. We donotvalidate the values of arguments
first,lastandssnbefore assigning them to the corresponding instance variables. We certainly could validate the first and last names—perhaps by ensuring that they’re of a rea- sonable length. Similarly, a social security number could be validated to ensure that it con- tains nine digits, with or without dashes (e.g.,123-45-6789or123456789).
CommissionEmployeeMethodEarnings
Method Earnings (lines 88–91) calculates a CommissionEmployee’s earnings. Line 90 multiplies thecommissionRateby thegrossSalesand returns the result.
CommissionEmployeeMethodToString
MethodToString(lines 94–101) is special—it’s one of the methods thateveryclass inherits directly or indirectly from class object, which is the root of the C# class hierarchy.
Section 11.7 summarizes classobject’s methods. MethodToStringreturns astringrepre- senting an object. It’s calledimplicitlyby an app whenever an object must be converted to a
stringrepresentation, such as inConsole’sWritemethod orstringmethodFormatusing a format item. Classobject’sToStringmethod returns astringthat includes the name of the object’s class. It’s primarily a placeholder that can be (and typically should be)overridden by a derived class to specify an appropriatestringrepresentation of the data in a derived class object. MethodToStringof classCommissionEmployeeoverrides (redefines) classob-
ject’sToStringmethod. When invoked, CommissionEmployee’sToStringmethod uses
stringmethodFormatto return astringcontaining information about theCommission- Employee. Line 97 uses the format specifierC(in"{6:C}") to formatgrossSalesas currency and the format specifierF2(in"{8:F2}") to format thecommissionRatewith two digits of precision to the right of the decimal point. To override a base-class method, a derived class must declare a method with keywordoverrideand with thesame signature(method name, number of parameters and parameter types)and return type as the base-class method—
object’sToStringmethod takesnoparameters and returns typestring, soCommissionEm- ployeedeclaresToStringwith the same parameter list and return type.
ClassCommissionEmployeeTest
Figure 11.5 tests classCommissionEmployee. Lines 10–11 create aCommissionEmployee
object and invoke its constructor (lines 14–23 of Fig. 11.4) to initialize it. We append the
Msuffix to the gross sales amount and the commission rate to indicate that the compiler
should treate these asdecimalliterals, rather thandoubles. Lines 16–22 useCommission- Employee’s properties to retrieve the object’s instance-variable values for output. Line 23 outputs the amount calculated by theEarningsmethod. Lines 25–26 invoke thesetac- cessors of the object’sGrossSalesandCommissionRateproperties to change the values of instance variablesgrossSalesandcommissionRate. Lines 28–29 output thestringrep- resentation of the updatedCommissionEmployee. When an object is output using a format item, the object’sToStringmethod is invokedimplicitlyto obtain the object’sstringrep- resentation. Line 30 outputs the earnings again.
Common Programming Error 11.1
It’s a compilation error to override a method with one that has adifferentaccess modifier.
Overriding a method with amore restrictiveaccess modifier would break theis-arela- tionship. If apublicmethod could be overridden as aprotectedorprivatemethod, the derived-class objects would not be able to respond to the same method calls as base-class objects. Once a method is declared in a base class, the methodmusthave thesameaccess modifier for all that class’s direct and indirect derived classes.
1 // Fig. 11.5: CommissionEmployeeTest.cs 2 // Testing class CommissionEmployee.
3 using System;
4
5 public class CommissionEmployeeTest 6 {
7 public static void Main( string[] args )
8 {
9 // instantiate CommissionEmployee object 10
11 12
13 // display CommissionEmployee data 14 Console.WriteLine(
15 "Employee information obtained by properties and methods: \n" );
16 Console.WriteLine( "First name is {0}", );
17 Console.WriteLine( "Last name is {0}", );
Fig. 11.5 | Testing classCommissionEmployee. (Part 1 of 2.)
CommissionEmployee employee = new CommissionEmployee( "Sue",
"Jones", "222-22-2222", 10000.00M, .06M );
employee.FirstName employee.LastName
11.4 Relationship between Base Classes and Derived Classes 415
11.4.2 Creating aBasePlusCommissionEmployeeClass without Using Inheritance
We now discuss the second part of our introduction to inheritance by declaring and testing the (completely new and independent) classBasePlusCommissionEmployee(Fig. 11.6), which contains a first name, last name, social security number, gross sales amount, com- mission rateandbase salary—“Base” in the class name stands for “base salary” not base class. ClassBasePlusCommissionEmployee’spublicservices include aBasePlusCommis- sionEmployeeconstructor (lines 16–26), methods Earnings (lines 113–116) and To-
String (lines 119–127), and public properties (lines 30–110) for the class’s private instance variablesfirstName, lastName, socialSecurityNumber,grossSales, commis-
sionRateandbaseSalary(declared in lines 8–13). These variables, properties and meth- ods encapsulate all the necessary features of a base-salaried commission employee. Note the similarity between this class and classCommissionEmployee(Fig. 11.4)—in this exam- ple,we do not yet exploit that similarity.
ClassBasePlusCommissionEmployeedoesnotspecify that it extendsobjectwith the syntax “: object” in line 6, so the classimplicitlyextendsobject. Also, like classCommis-
sionEmployee’s constructor (lines 14–23 of Fig. 11.4), class BasePlusCommissionEm- 18 Console.WriteLine( "Social security number is {0}",
19 );
20 Console.WriteLine( "Gross sales are {0:C}", );
21 Console.WriteLine( "Commission rate is {0:F2}",
22 );
23 Console.WriteLine( "Earnings are {0:C}", );
24 25 26 27 28 29 30
31 } // end Main
32 } // end class CommissionEmployeeTest
Employee information obtained by properties and methods:
First name is Sue Last name is Jones
Social security number is 222-22-2222 Gross sales are $10,000.00
Commission rate is 0.06 Earnings are $600.00
Updated employee information obtained by ToString:
commission employee: Sue Jones social security number: 222-22-2222 gross sales: $5,000.00
commission rate: 0.10 earnings: $500.00
Fig. 11.5 | Testing classCommissionEmployee. (Part 2 of 2.)
employee.SocialSecurityNumber
employee.GrossSales employee.CommissionRate
employee.Earnings() employee.GrossSales = 5000.00M; // set gross sales
employee.CommissionRate = .1M; // set commission rate Console.WriteLine( "\n{0}:\n\n{1}",
"Updated employee information obtained by ToString", employee );
Console.WriteLine( "earnings: {0:C}", employee.Earnings() );
ployee’s constructor invokes classobject’s default constructorimplicitly, as noted in the comment in line 19 of Fig. 11.6.
1 // Fig. 11.6: BasePlusCommissionEmployee.cs
2 // BasePlusCommissionEmployee class represents an employee that receives 3 // a base salary in addition to a commission.
4 using System;
5
6 public class BasePlusCommissionEmployee 7 {
8 private string firstName;
9 private string lastName;
10 private string socialSecurityNumber;
11 private decimal grossSales; // gross weekly sales 12 private decimal commissionRate; // commission percentage 13
14
15 // six-parameter constructor
16 public BasePlusCommissionEmployee( string first, string last, 17 string ssn, decimal sales, decimal rate, decimal salary )
18 {
19 // implicit call to object constructor occurs here 20 firstName = first;
21 lastName = last;
22 socialSecurityNumber = ssn;
23 GrossSales = sales; // validate gross sales via property 24 CommissionRate = rate; // validate commission rate via property 25
26 } // end six-parameter BasePlusCommissionEmployee constructor 27
28 // read-only property that gets
29 // BasePlusCommissionEmployee's first name 30 public string FirstName
31 {
32 get
33 {
34 return firstName;
35 } // end get
36 } // end property FirstName 37
38 // read-only property that gets
39 // BasePlusCommissionEmployee's last name 40 public string LastName
41 {
42 get
43 {
44 return lastName;
45 } // end get
46 } // end property LastName 47
Fig. 11.6 | BasePlusCommissionEmployeeclass represents an employee that receives a base salary in addition to a commission. (Part 1 of 3.)
private decimal baseSalary; // base salary per week
BaseSalary = salary; // validate base salary via property
11.4 Relationship between Base Classes and Derived Classes 417
48 // read-only property that gets
49 // BasePlusCommissionEmployee's social security number 50 public string SocialSecurityNumber
51 {
52 get
53 {
54 return socialSecurityNumber;
55 } // end get
56 } // end property SocialSecurityNumber 57
58 // property that gets and sets
59 // BasePlusCommissionEmployee's gross sales 60 public decimal GrossSales
61 {
62 get
63 {
64 return grossSales;
65 } // end get
66 set
67 {
68 if ( value >= 0 )
69 grossSales = value;
70 else
71 throw new ArgumentOutOfRangeException(
72 "GrossSales", value, "GrossSales must be >= 0" );
73 } // end set
74 } // end property GrossSales 75
76 // property that gets and sets
77 // BasePlusCommissionEmployee's commission rate 78 public decimal CommissionRate
79 {
80 get
81 {
82 return commissionRate;
83 } // end get
84 set
85 {
86 if ( value > 0 && value < 1 ) 87 commissionRate = value;
88 else
89 throw new ArgumentOutOfRangeException( "CommissionRate", 90 value, "CommissionRate must be > 0 and < 1" );
91 } // end set
92 } // end property CommissionRate 93
94 // property that gets and sets
95 // BasePlusCommissionEmployee's base salary 96
97 98 99
Fig. 11.6 | BasePlusCommissionEmployeeclass represents an employee that receives a base salary in addition to a commission. (Part 2 of 3.)
public decimal BaseSalary {
get {
ClassBasePlusCommissionEmployee’sEarningsmethod (lines 113–116) computes the earnings of a base-salaried commission employee. Line 115 adds the employee’s base salary to the product of the commission rate and the gross sales, and returns the result.
ClassBasePlusCommissionEmployeeoverridesobjectmethodToStringto return a
string containing the BasePlusCommissionEmployee’s information (lines 119–127).
Once again, we use format specifierCto format the gross sales and base salary as currency and format specifierF2to format the commission rate with two digits of precision to the right of the decimal point (line 122).
ClassBasePlusCommissionEmployeeTest
Figure 11.7 tests class BasePlusCommissionEmployee. Lines 10–12 instantiate a Base-
PlusCommissionEmployee object and pass "Bob", "Lewis", "333-33-3333", 5000.00M,
.04Mand300.00Mto the constructor as the first name, last name, social security number, gross sales, commission rate and base salary, respectively. Lines 17–25 useBasePlusCom- missionEmployee’s properties and methods to retrieve the values of the object’s instance variables and calculate the earnings for output. Line 27 invokes the object’sBaseSalary property to changethe base salary. PropertyBaseSalary’s setaccessor (Fig. 11.6, lines 102–109) ensures that instance variablebaseSalaryis not assigned a negative value, be- 100
101 102 103 104 105 106 107 108 109 110 111
112 // calculate earnings 113 public decimal Earnings()
114 {
115
116 } // end method Earnings 117
118 // return string representation of BasePlusCommissionEmployee 119 public override string ToString()
120 {
121 return string.Format(
122 "{0}: {1} {2}\n{3}: {4}\n{5}: {6:C}\n{7}: {8:F2}\n{9}: {10:C}", 123 "base-salaried commission employee", firstName, lastName, 124 "social security number", socialSecurityNumber,
125 "gross sales", grossSales, "commission rate", commissionRate,
126 );
127 } // end method ToString
128 } // end class BasePlusCommissionEmployee
Fig. 11.6 | BasePlusCommissionEmployeeclass represents an employee that receives a base salary in addition to a commission. (Part 3 of 3.)
return baseSalary;
} // end get set
{
if ( value >= 0 ) baseSalary = value;
else
throw new ArgumentOutOfRangeException( "BaseSalary", value, "BaseSalary must be >= 0" );
} // end set
} // end property BaseSalary
return baseSalary + ( commissionRate * grossSales );
"base salary", baseSalary
11.4 Relationship between Base Classes and Derived Classes 419
cause an employee’s base salary cannot be negative. Lines 29–30 of Fig. 11.7 invoke the ob- ject’sToStringmethodimplicitlyto get the object’sstringrepresentation.
1 // Fig. 11.7: BasePlusCommissionEmployeeTest.cs 2 // Testing class BasePlusCommissionEmployee.
3 using System;
4
5 public class BasePlusCommissionEmployeeTest 6 {
7 public static void Main( string[] args )
8 {
9 // instantiate BasePlusCommissionEmployee object 10
11 12 13
14 // display BasePlusCommissionEmployee's data 15 Console.WriteLine(
16 "Employee information obtained by properties and methods: \n" );
17 Console.WriteLine( "First name is {0}", );
18 Console.WriteLine( "Last name is {0}", );
19 Console.WriteLine( "Social security number is {0}",
20 );
21 Console.WriteLine( "Gross sales are {0:C}", );
22 Console.WriteLine( "Commission rate is {0:F2}",
23 );
24 Console.WriteLine( "Earnings are {0:C}", );
25 Console.WriteLine( "Base salary is {0:C}", );
26 27 28
29 Console.WriteLine( "\n{0}:\n\n{1}",
30 "Updated employee information obtained by ToString", );
31 Console.WriteLine( "earnings: {0:C}", employee.Earnings() );
32 } // end Main
33 } // end class BasePlusCommissionEmployeeTest
Employee information obtained by properties and methods:
First name is Bob Last name is Lewis
Social security number is 333-33-3333 Gross sales are $5,000.00
Commission rate is 0.04 Earnings are $500.00 Base salary is $300.00
Updated employee information obtained by ToString:
base-salaried commission employee: Bob Lewis social security number: 333-33-3333
gross sales: $5,000.00 commission rate: 0.04 base salary: $1,000.00 earnings: $1,200.00
Fig. 11.7 | Testing classBasePlusCommissionEmployee.
BasePlusCommissionEmployee employee =
new BasePlusCommissionEmployee( "Bob", "Lewis",
"333-33-3333", 5000.00M, .04M, 300.00M );
employee.FirstName employee.LastName employee.SocialSecurityNumber
employee.GrossSales employee.CommissionRate
employee.Earnings() employee.BaseSalary employee.BaseSalary = 1000.00M; // set base salary
employee
Much of the code for classBasePlusCommissionEmployee(Fig. 11.6) is similar, if not identical, to the code for classCommissionEmployee(Fig. 11.4). For example, in classBase-
PlusCommissionEmployee,privateinstance variablesfirstNameandlastNameand prop- ertiesFirstNameandLastNameare identical to those of classCommissionEmployee. Classes
CommissionEmployee and BasePlusCommissionEmployee also both contain private instance variablessocialSecurityNumber, commissionRate and grossSales, as well as properties to manipulate these variables. In addition, theBasePlusCommissionEmployee
constructor is almost identical to that of classCommissionEmployee, except that Base-
PlusCommissionEmployee’s constructor also sets thebaseSalary. The other additions to classBasePlusCommissionEmployeeareprivateinstance variablebaseSalaryand prop- ertyBaseSalary. ClassBasePlusCommissionEmployee’sEarningsmethod is nearly iden- tical to that of classCommissionEmployee, except thatBasePlusCommissionEmployee’s also adds thebaseSalary. Similarly, classBasePlusCommissionEmployee’sToStringmethod is nearly identical to that of classCommissionEmployee, except thatBasePlusCommissionEm- ployee’sToStringalso formats the value of instance variablebaseSalaryas currency.
We literallycopiedthe code from classCommissionEmployeeandpastedit into class
BasePlusCommissionEmployee, then modified class BasePlusCommissionEmployee to include a base salary and methods and properties that manipulate the base salary. This
“copy-and-paste” approach is often error prone and time consuming. Worse yet, it can spread many physical copies of the same code throughout a system, creating a code-main- tenance nightmare. Is there a way to “absorb” the members of one class in a way that makes them part of other classes without copying code? In the next several examples we answer this question, using a more elegant approach to building classes—namely,inheritance.
11.4.3 Creating aCommissionEmployee–
BasePlusCommissionEmployeeInheritance Hierarchy
Now we declare classBasePlusCommissionEmployee(Fig. 11.8), which extends classCom-
missionEmployee(Fig. 11.4). ABasePlusCommissionEmployeeobjectis aCommissionEm- ployee(because inheritance passes on the capabilities of classCommissionEmployee), but classBasePlusCommissionEmployeealso has instance variablebaseSalary(Fig. 11.8, line 7). The colon (:) in line 5 of the class declaration indicates inheritance. As a derived class,
BasePlusCommissionEmployeeinherits the members of classCommissionEmployeeand can access those members that are non-private. The constructor of classCommissionEmployee
Error-Prevention Tip 11.1
Copying and pasting code from one class to another can spread errors across multiple source-code files. To avoid duplicating code (and possibly errors) in situations where you want one class to “absorb” the members of another class, use inheritance rather than the
“copy-and-paste” approach.
Software Engineering Observation 11.3
With inheritance, the common members of all the classes in the hierarchy are declared in a base class. When changes are required for these common features, you need to make the changes only in the base class—derived classes then inherit the changes. Without inheritance, changes would need to be made to all the source-code files that contain a copy of the code in question.