11.4 Relationship between Base Classes and Derived Classes

In this section, we use an inheritance hierarchy containing types of employees in a company’s payroll app to discuss the relationship between a base class and a derived class. In this company:

  • commission employees—who will be represented as objects of a base class—are paid a percentage of their sales, while

  • base-salaried commission employees—who will be represented as objects of a derived class—receive a base salary plus a percentage of their sales.

We divide our discussion of the relationship between these employee types into an evolutionary set of five examples that was carefully designed to teach key capabilities for good software engineering with inheritance:

  1. The first example creates class CommissionEmployee, which directly inherits from class object. The class declares public auto-implemented properties for the first name, last name and social security number, and private instance variables for the commission rate and gross (i.e., total) sales amount.

  2. The second example declares class BasePlusCommissionEmployee, which also directly inherits from object. The class declares public auto-implemented properties for the first name, last name and social security number, and private instance variables for the commission rate, gross sales amount and base salary. We create the class by writing every line of code the class requires—we’ll soon see that it’s more efficient to create this class by inheriting from CommissionEmployee.

  3. The third example declares a separate BasePlusCommissionEmployee class that extends class CommissionEmployee—that is, a BasePlusCommissionEmployee is a CommissionEmployee who also has a base salary. BasePlusCommissionEmployee attempts to access class CommissionEmployee’s private members, but this results in compilation errors because a derived class cannot access its base class’s private instance variables.

  4. The fourth example shows that if base class CommissionEmployee’s instance variables are declared as protected, a BasePlusCommissionEmployee class that inherits from CommissionEmployee can access that data directly.

  5. The fifth and final example demonstrates best practice by setting the Commission-Employee instance variables back to private in class CommissionEmployee to enforce good software engineering. Then we show how a separate BasePlusCommissionEmployee class that inherits from class CommissionEmployee can use

    CommissionEmployee’s public methods and properties to manipulate CommissionEmployee’s private instance variables in a controlled manner.

In the first four examples, we’ll directly access instance variables in several cases where properties should be used. In the fifth example, we’ll apply effective software engineering techniques that we’ve presented up to this point in the book to create classes that are easy to maintain, modify and debug.

11.4.1 Creating and Using a CommissionEmployee Class

We begin with class CommissionEmployee (Fig. 11.4). The colon (:) followed by class name object in line 5 indicates that class CommissionEmployee extends (i.e., inherits from) class object (an alias for class Object in namespace System). You use inheritance to create classes from existing classes. Every class (except object) extends an existing class. Because class CommissionEmployee extends object, class CommissionEmployee inherits object’s methods—object has no fields. (Section 11.7 summarizes object’s methods.) Every C# class directly or indirectly inherits object’s methods. If a class does not specify that it inherits from another class, the new class implicitly inherits from object. For this reason, you typically do not include “: object” in your code—we do so in this example, then not again in the book.

Fig. 11.4 CommissionEmployee class represents a commission employee.

Alternate View

  1    // Fig. 11.4: CommissionEmployee.cs
  2    // CommissionEmployee class represents a commission employee.
  3    using System;
  4
  5    public class CommissionEmployee : object
  6    {
  7       public string FirstName { get; }
  8       public string LastName { get; }
  9       public string SocialSecurityNumber { get; }
 10       private decimal grossSales; // gross weekly sales       
 11       private decimal commissionRate; // commission percentage
 12
 13       // five-parameter constructor
 14       public CommissionEmployee(string firstName, string lastName,
 15          string socialSecurityNumber, decimal grossSales,
 16          decimal commissionRate)
 17       {
 18          // implicit call to object constructor occurs here
 19          FirstName = firstName;
 20          LastName = lastName;
 21          SocialSecurityNumber = socialSecurityNumber;
 22          GrossSales = grossSales; // validates gross sales
 23          CommissionRate = commissionRate; // validates commission rate
 24       }
 25
 26       // property that gets and sets commission employee's gross sales
 27       public decimal GrossSales
 28       {
 29          get
 30          {
 31             return grossSales;
 32          }
 33          set
 34          {
 35             if (value < 0) // validation
 36             {
 37                throw new ArgumentOutOfRangeException(nameof(value),
 38                   value, $"{nameof(GrossSales)} must be >= 0");
 39             }
 40
 41                grossSales = value;
 42          }
 43       }
 44
 45       // property that gets and sets commission employee's commission rate
 46       public decimal CommissionRate
 47       {
 48          get
 49          {
 50             return commissionRate;
 51          }
 52          set
 53          {
 54             if (value <= 0 || value >= 1) // validation
 55             {
 56                throw new ArgumentOutOfRangeException(nameof(value),
 57                   value, $"{nameof(CommissionRate)} must be > 0 and < 1");
 58             }
 59
 60             commissionRate = value;
 61          }
 62       }
 63
 64       // calculate commission employee's pay
 65       public decimal Earnings() => commissionRate * grossSales;
 66
 67       // return string representation of CommissionEmployee object
 68       public override string ToString() =>
 69          $"commission employee: {FirstName} {LastName}
" +
 70          $"social security number: {SocialSecurityNumber}
" +
 71          $"gross sales: {grossSales:C}
" +
 72          $"commission rate: {commissionRate:F2}";
 73    }

CommissionEmployee Class Overview

CommissionEmployee’s attributes include public, getter-only, auto-implemented properties FirstName, LastName and SocialSecurityNumber, and private instance variables grossSales and commissionRate. The class provides

  • a constructor (lines 14–24)

  • public properties (lines 27–62) to set and get grossSales and commissionRate, and

  • expression-bodied methods Earnings (line 65) and ToString (lines 68–72).

Because instance variables grossSales and commissionRate are private, other classes cannot directly access these variables. Declaring instance variables as private and providing public properties to manipulate and validate them helps enforce good software engineering. The set accessors of properties GrossSales and CommissionRate validate their arguments before assigning the values to instance variables grossSales and commissionRate, respectively.

CommissionEmployee Constructor

Constructors are not inherited, so class CommissionEmployee does not inherit class object’s constructor. However, class CommissionEmployee’s constructor calls object’s constructor implicitly. In fact, before executing the code in its own body, every derived class’s constructor calls a constructor in its direct base class, either explicitly or implicitly (if no constructor call is specified), to ensure that the instance variables inherited from the base class are initialized properly.

Calling a base-class constructor explicitly is discussed in Section 11.4.3. If the code does not explicitly call the base-class constructor, the compiler generates an implicit call to the base class’s default or parameterless constructor, or a constructor with all default arguments. The comment in line 18 indicates where the implicit call to the base class object’s constructor is made (you do not write the code for this call). 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. Class object is the only class that does not have a base class.

After the implicit call to object’s constructor occurs, lines 19–23 in the constructor assign values to the class’s properties. We do not validate the values of arguments first-Name, lastName and socialSecurityNumber. We certainly could validate the first and last names—perhaps by ensuring that they’re of a reasonable length. Similarly, a social security number could be validated to ensure that it contains nine digits, with or without dashes (e.g., 123-45-6789 or 123456789).

CommissionEmployee Method Earnings

Method Earnings (line 65) calculates a CommissionEmployee’s earnings by multiplying the commissionRate and the grossSales, then returns the result. As a best practice, we should use the properties CommissionRate and GrossSales to access the instance variables in line 65. We access them directly here and in the next few examples to help motivate then demonstrate the protected access modifier. In this chapter’s final example, we’ll follow best practice.

CommissionEmployee Method ToString

Method ToString (lines 68–72) is special—it’s one of the methods that every class inherits directly or indirectly from class object. Method ToString returns a string representing an object. It can be called explicitly, but it’s called implicitly by an app whenever an object must be converted to a string representation, such as when displaying an object with Console’s Write or WriteLine methods or inserting an object in a string-interpolation expression. By default, class object’s ToString method returns the object’s fully qualified class name. For object, ToString returns


System.Object

because object is an alias for class Object in the System namespace. ToString is primarily a placeholder that can be (and typically should be) overridden by a derived class to specify an appropriate string representation of the data in a derived class object.

Method ToString of class CommissionEmployee overrides (redefines) class object’s ToString method. When invoked, CommissionEmployee’s ToString method returns a string containing information about the CommissionEmployee. Line 71 uses the format specifier C (in "{grossSales:C}") to format grossSales as currency and line 72 uses the format specifier F2 (in "{commissionRate:F2}") to format the commissionRate with two digits to the right of the decimal point.

To override a base-class method, a derived class must declare a method with keyword override and with the same signature (method name, number of parameters and parameter types) and return type as the base-class method—object’s ToString method takes no parameters and returns type string, so CommissionEmployee declares ToString with the same parameter list and return type. As you’ll soon see, the base-class method also must be declared virtual, which is the case for object method ToString.

Common Programming Error 11.1

It’s a compilation error to override a method with one that has a different access modifier. Overriding a method with a more restrictive access modifier would break the is-a relationship. If a public method could be overridden as a protected or private method, the derived-class objects would not be able to respond to the same method calls as base-class objects. In particular, once a method is declared in a base class, the method must have the same access modifier for all that class’s direct and indirect derived classes.

Class CommissionEmployeeTest

Figure 11.5 tests class CommissionEmployee. Lines 10–11 create a CommissionEmployee object and invoke its constructor (lines 14–24 of Fig. 11.4) to initialize it. Again, we append the M suffix to the gross sales amount and the commission rate to indicate these are decimal literals. Lines 16–22 in Fig. 11.5 use CommissionEmployee’s properties to retrieve the object’s instance-variable values for output. Line 23 outputs the amount calculated by the Earnings method. Lines 25–26 invoke the set accessors of the object’s GrossSales and CommissionRate properties to change the values of instance variables grossSales and commissionRate. Line 30 outputs the string representation of the updated CommissionEmployee. When an object is output by Console’s WriteLine method, which displays the object’s string representation, the ToString method is invoked implicitly. Line 31 outputs the updated earnings.

Fig. 11.5 Testing class CommissionEmployee

Alternate View

   1   // Fig. 11.5: CommissionEmployeeTest.cs
   2   // Testing class CommissionEmployee.
   3   using System;
   4
   5   class CommissionEmployeeTest
   6   {
   7      static void Main()
   8      {
   9         // instantiate CommissionEmployee object
  10         var employee = new CommissionEmployee("Sue", "Jones",
  11             "222-22-2222", 10000.00M, .06M);                 
  12
  13         // display CommissionEmployee data
  14         Console.WriteLine(
  15            "Employee information obtained by properties and methods: 
");
  16         Console.WriteLine($"First name is {employee.FirstName}");
  17         Console.WriteLine($"Last name is {employee.LastName}");
  18         Console.WriteLine(
  19            $"Social security number is {employee.SocialSecurityNumber}");
  20         Console.WriteLine($"Gross sales are {employee.GrossSales:C}");
  21         Console.WriteLine(
  22            $"Commission rate is {employee.CommissionRate:F2}");
  23         Console.WriteLine($"Earnings are {employee.Earnings():C}");
  24
  25         employee.GrossSales = 5000.00M; // set gross sales   
  26         employee.CommissionRate = .1M; // set commission rate
  27
  28         Console.WriteLine(
  29            "
Updated employee information obtained by ToString:
");
  30         Console.WriteLine(employee);
  31         Console.WriteLine($"earnings: {employee.Earnings():C}");
  32       }
  33    }

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

11.4.2 Creating a BasePlusCommissionEmployee Class without Using Inheritance

We now discuss the second part of our introduction to inheritance by declaring and testing the completely new and independent class BasePlusCommissionEmployee (Fig. 11.6), which contains a first name, last name, social security number, gross sales amount, commission rate and base salary—“Base” in the class name stands for “base salary” not base class.

Fig. 11.6 BasePlusCommissionEmployee class represents an employee that receives a base salary in addition to a commission.

Alternate View

   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      public string FirstName { get; }
   9      public string LastName { get; }
  10      public string SocialSecurityNumber { get; }
  11      private decimal grossSales; // gross weekly sales
  12      private decimal commissionRate; // commission percentage
  13      private decimal baseSalary; // base salary per week
  14
  15      // six-parameter constructor
  16      public BasePlusCommissionEmployee(string firstName, string lastName,
  17         string socialSecurityNumber, decimal grossSales,
  18         decimal commissionRate, decimal baseSalary)
  19      {
  20         // implicit call to object constructor occurs here
  21         FirstName = firstName;
  22         LastName = lastName;
  23         SocialSecurityNumber = socialSecurityNumber;
  24         GrossSales = grossSales; // validates gross sales
  25         CommissionRate = commissionRate; // validates commission rate
  26         BaseSalary = baseSalary; // validates base salary
  27      }
  28
  29      // property that gets and sets gross sales
  30      public decimal GrossSales
  31      {
  32         get
  33         {
  34            return grossSales;
  35         }
  36         set
  37         {
  38            if (value < 0) // validation
  39            {
  40               throw new ArgumentOutOfRangeException(nameof(value),
  41                  value, $"{nameof(GrossSales)} must be >= 0");
  42            }
  43
  44            grossSales = value;
  45         }
  46      }
  47
  48      // property that gets and sets commission rate
  49      public decimal CommissionRate
  50      {
  51         get
  52         {
  53            return commissionRate;
  54         }
  55         set
  56         {
  57            if (value <= 0 || value >= 1) // validation
  58            {
  59               throw new ArgumentOutOfRangeException(nameof(value),
  60                  value, $"{nameof(CommissionRate)} must be > 0 and < 1");
  61            }
  62
  63            commissionRate = value;
  64         }
  65      }
  66
  67      // property that gets and sets BasePlusCommissionEmployee's base salary
  68      public decimal BaseSalary
  69      {
  70         get
  71         {
  72            return baseSalary;
  73         }
  74         set
  75         {
  76            if (value < 0) // validation
  77            {
  78               throw new ArgumentOutOfRangeException(nameof(value),
  79                  value, $"{nameof(BaseSalary)} must be >= 0");
  80            }
  81
  82            baseSalary = value;
  83         }
  84      }
  85
  86      // calculate earnings
  87      public decimal Earnings() =>
  88         baseSalary + (commissionRate * grossSales);
  89
  90      // return string representation of BasePlusCommissionEmployee
  91      public override string ToString() =>
  92         $"base-salaried commission employee: {FirstName} {LastName}
" +
  93         $"social security number: {SocialSecurityNumber}
" +
  94         $"gross sales: {grossSales:C}
" +
  95         $"commission rate: {commissionRate:F2}
" +
  96         $"base salary: {baseSalary:C}";
  97   }

BasePlusCommissionEmployee’s attributes include public, getter-only, auto-implemented properties for the FirstName, LastName and SocialSecurityNumber, and private instance variables for the grossSales, commissionRate and baseSalary. The class provides

  • a constructor (lines 16–27)

  • public properties (lines 30–84) for manipulating the grossSales, commission-Rate and baseSalary, and

  • expression-bodied methods Earnings (lines 87–88) and ToString (lines 91–96).

Instance variables grossSales, commissionRate and baseSalary are private, so objects of other classes cannot directly access these variables. The set accessors of properties Gross-Sales, CommissionRate and BaseSalary validate their arguments before assigning the values to instance variables grossSales, commissionRate and baseSalary, respectively.

BasePlusCommissionEmployee’s variables, properties and methods encapsulate all the necessary features of a base-salaried commission employee. Note the similarity between this class and class CommissionEmployee (Fig. 11.4)—in this example, we do not yet exploit that similarity.

Class BasePlusCommissionEmployee does not specify that it extends object with the syntax “: object” in line 6, so the class implicitly extends object. Also, like class CommissionEmployee’s constructor (lines 14–24 of Fig. 11.4), class BasePlusCommissionEmployee’s constructor invokes class object’s default constructor implicitly, as noted in the comment in line 20 of Fig. 11.6.

Class BasePlusCommissionEmployee’s Earnings method (lines 87–88) computes the earnings of a base-salaried commission employee. Line 88 adds the employee’s base salary to the product of the commission rate and the gross sales, and returns the result.

Class BasePlusCommissionEmployee overrides object method ToString (lines 91–96) to return a string containing the BasePlusCommissionEmployee’s information. Once again, we use format specifier C to format the gross sales and base salary as currency and format specifier F2 to format the commission rate with two digits of precision to the right of the decimal point.

Class BasePlusCommissionEmployeeTest

Figure 11.7 tests class BasePlusCommissionEmployee. Lines 10–11 instantiate a BasePlusCommissionEmployee object and pass "Bob", "Lewis", "333-33-3333", 5000.00M, .04M and 300.00M to the constructor as the first name, last name, social security number, gross sales, commission rate and base salary, respectively. Lines 16–24 use BasePlusCommissionEmployee’s properties and methods to retrieve the values of the object’s instance variables and calculate the earnings for output. Line 26 invokes the object’s BaseSalary property to change the base salary. Property BaseSalary’s set accessor (Fig. 11.6, lines 68–84) ensures that instance variable baseSalary is not assigned a negative value, because an employee’s base salary cannot be negative. Lines 30–31 of Fig. 11.7 invoke the object’s To-String method implicitly and invoke the object’s Earnings method.

Fig. 11.7 Testing class BasePlusCommissionEmployee.

Alternate View

   1   // Fig. 11.7: BasePlusCommissionEmployeeTest.cs
   2   // Testing class BasePlusCommissionEmployee.
   3   using System;
   4
   5   class BasePlusCommissionEmployeeTest
   6   {
   7      static void Main()
   8      {
   9         // instantiate BasePlusCommissionEmployee object
  10         var employee = new BasePlusCommissionEmployee("Bob", "Lewis", 
  11             "333-33-3333", 5000.00M, .04M, 300.00M);                  
  12
  13         // display BasePlusCommissionEmployee's data
  14         Console.WriteLine(
  15            "Employee information obtained by properties and methods: 
");
  16         Console.WriteLine($"First name is {employee.FirstName}");
  17         Console.WriteLine($"Last name is {employee.LastName}");
  18         Console.WriteLine(
  19            $"Social security number is {employee.SocialSecurityNumber}");
  20         Console.WriteLine($"Gross sales are {employee.GrossSales:C}");
  21         Console.WriteLine(
  22            $"Commission rate is {employee.CommissionRate:F2}");
  23         Console.WriteLine($"Earnings are {employee.Earnings():C}");
  24         Console.WriteLine($"Base salary is {employee.BaseSalary:C}");
  25
  26         employee.BaseSalary = 1000.00M; // set base salary
  27
  28         Console.WriteLine(
  29            "
Updated employee information obtained by ToString:
");
  30         Console.WriteLine(employee);
  31         Console.WriteLine($"earnings: {employee.Earnings():C}");
  32      }
  33   }

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

Code Duplication

Much of the code for class BasePlusCommissionEmployee (Fig. 11.6) is similar, or identical, to that of class CommissionEmployee (Fig. 11.4). For example, in class BasePlusCommissionEmployee, properties FirstName, LastName and SocialSecurityNumber are identical to those of class CommissionEmployee. Classes CommissionEmployee and BasePlusCommissionEmployee also both contain private instance variables commissionRate and grossSales, as well as identical properties to manipulate these variables. In addition, the BasePlusCommissionEmployee constructor is almost identical to that of class CommissionEmployee, except that BasePlusCommissionEmployee’s constructor also sets the BaseSalary.

The other additions to class BasePlusCommissionEmployee are private instance variable baseSalary and public property BaseSalary. Class BasePlusCommissionEmployee’s Earnings method is nearly identical to that of class CommissionEmployee, except that BasePlusCommissionEmployee’s Earnings also adds the baseSalary. Similarly, class BasePlusCommissionEmployee’s ToString method is nearly identical to that of class CommissionEmployee, except that BasePlusCommissionEmployee’s ToString also formats the value of instance variable baseSalary as currency.

We literally copied the code from class CommissionEmployee and pasted it 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 error prone and time consuming. Worse yet, it can spread many physical copies of the same code throughout a system, creating a code-maintenance 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.

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.

11.4.3 Creating a CommissionEmployeeBasePlusCommissionEmployee Inheritance Hierarchy

Now we declare class BasePlusCommissionEmployee (Fig. 11.8), which extends (inherits from) class CommissionEmployee (Fig. 11.4).1 A BasePlusCommissionEmployee object is a CommissionEmployee (because inheritance passes on the capabilities of class CommissionEmployee), but class BasePlusCommissionEmployee also has instance variable baseSalary (Fig. 11.8, line 7). The colon (:) in line 5 of the class declaration indicates inheritance. As a derived class, BasePlusCommissionEmployee inherits CommissionEmployee’s members and can access only those that are non-private. CommissionEmployee’s constructor is not inherited. Thus, the public services of BasePlusCommissionEmployee include its constructor (lines 11–18), public methods and properties inherited from class CommissionEmployee, property BaseSalary (lines 22–38), method Earnings (lines 41–42) and method ToString (lines 45–51). We’ll explain the errors in Fig. 11.8 momentarily.

Fig. 11.8 BasePlusCommissionEmployee inherits from CommissionEmployee.

Alternate View

   1   // Fig. 11.8: BasePlusCommissionEmployee.cs
   2   // BasePlusCommissionEmployee inherits from CommissionEmployee.
   3   using System;
   4
   5   public class BasePlusCommissionEmployee : CommissionEmployee
   6   {
   7      private decimal baseSalary; // base salary per week
   8
   9      // six-parameter derived-class constructor
  10      // with call to base class CommissionEmployee constructor
  11      public BasePlusCommissionEmployee(string firstName, string lastName,
  12         string socialSecurityNumber, decimal grossSales,
  13         decimal commissionRate, decimal baseSalary)
  14         : base(firstName, lastName, socialSecurityNumber,
  15             grossSales, commissionRate)                  
  16      {
  17         BaseSalary = baseSalary; // validates base salary
  18      }
  19
  20      // property that gets and sets
  21      // BasePlusCommissionEmployee's base salary
  22      public decimal BaseSalary
  23      {
  24         get
  25         {
  26            return baseSalary;
  27         }
  28         set
  29         {
  30            if (value < 0) // validation
  31            {
  32               throw new ArgumentOutOfRangeException(nameof(value),
  33                  value, $"{nameof(BaseSalary)} must be >= 0");
  34            }
  35
  36            baseSalary = value;
  37         }
  38      }
  39
  40      // calculate earnings
  41      public override decimal Earnings() =>
  42         baseSalary + (commissionRate * grossSales);
  43
  44      // return string representation of BasePlusCommissionEmployee
  45      public override string ToString() =>
  46         // not allowed: attempts to access private base-class members
  47         $"base-salaried commission employee: {FirstName} {LastName}
" +
  48         $"social security number: {SocialSecurityNumber}
" +
  49         $"gross sales: {grossSales:C}
" +
  50         $"commission rate: {commissionRate:F2}
" +
  51         $"base salary: {baseSalary}";
  52   }

A Derived Class’s Constructor Must Call Its Base Class’s Constructor

Each derived-class constructor must implicitly or explicitly call its base-class constructor to ensure that the instance variables inherited from the base class are initialized properly. BasePlusCommissionEmployee’s six-parameter constructor explicitly calls class CommissionEmployee’s five-parameter constructor to initialize the CommissionEmployee portion of a BasePlusCommissionEmployee object—that is, the FirstName, LastName, SocialSecurityNumber, GrossSales and CommissionRate.

Lines 14–15 in BasePlusCommissionEmployee’s constructor invoke CommissionEmployee’s constructor (declared at lines 14–24 of Fig. 11.4) via a constructor initializer. In Section 10.5, we used a constructor initializer with keyword this to call an overloaded constructor in the same class. Line 14 of Fig. 11.8 uses a constructor initializer with keyword base to invoke the base-class constructor, passing arguments to initialize the base class’s corresponding properties that were inherited into the derived-class object. If BasePlusCommissionEmployee’s constructor did not invoke CommissionEmployee’s constructor explicitly, C# would attempt to invoke class CommissionEmployee’s parameterless or default constructor implicitly. CommissionEmployee does not have such a constructor, so the compiler would issue an error.

Common Programming Error 11.2

A compilation error occurs if a derived-class constructor calls one of its base-class constructors with arguments that do not match the number and types of parameters specified in one of the base-class constructor declarations.

BasePlusCommissionEmployee Method Earnings

Lines 41–42 of Fig. 11.8 declare method Earnings using keyword override to override the CommissionEmployee’s Earnings method, as we did with method ToString in previous examples. Line 41 causes the first compilation error shown in Fig. 11.8, indicating that we cannot override the base class’s Earnings method because it was not explicitly “marked virtual, abstract, or override.” The virtual and abstract keywords indicate that a base-class method can be overridden in derived classes.2 The override modifier declares that a derived-class method overrides a virtual or abstract base-class method. This modifier also implicitly declares the derived-class method virtual and allows it to be overridden in derived classes further down the inheritance hierarchy. Adding the keyword virtual to method Earnings’ declaration in Fig. 11.4, as in


public virtual decimal Earnings()

eliminates the first compilation error in Fig. 11.8.

The compiler generates additional errors for line 42 of Fig. 11.8, because base class CommissionEmployee’s instance variables commissionRate and grossSales are private—derived class BasePlusCommissionEmployee’s methods are not allowed to access the base class’s private members. The compiler also issues errors at lines 49–50 in method ToString for the same reason. The errors in BasePlusCommissionEmployee could have been prevented by using the public properties inherited from class CommissionEmployee. For example, line 42, 49 and 50 could have invoked the get accessors of properties CommissionRate and GrossSales to access CommissionEmployee’s private instance variables commissionRate and grossSales, respectively.

11.4.4 CommissionEmployeeBasePlusCommissionEmployee Inheritance Hierarchy Using protected Instance Variables

To enable class BasePlusCommissionEmployee to directly access base-class instance variables grossSales and commissionRate, we can declare those members as protected in the base class. As we discussed in Section 11.3, a base class’s protected members are accessible to all derived classes of that base class. Class CommissionEmployee in this example is a modification of the version from Fig. 11.4 that declares its instance variables gross-Sales and commissionRate as


protected decimal grossSales; // gross weekly sales
protected decimal commissionRate; // commission percentage

rather than private. We also declare the Earnings method virtual so that BasePlusCommissionEmployee can override the method. The rest of class CommissionEmployee in this example is identical to Fig. 11.4. The complete source code for class CommissionEmployee is included in this example’s project.

Class BasePlusCommissionEmployee

Class BasePlusCommissionEmployee (Fig. 11.9) in this example extends the version of class CommissionEmployee with protected instance variables grossSales and commissionRate. Each BasePlusCommissionEmployee object contains these CommissionEmployee instance variables, which are inherited into BasePlusCommissionEmployee as protected members. As a result, directly accessing instance variables grossSales and commissionRate no longer generates compilation errors in methods Earnings and ToString. If another class extends BasePlusCommissionEmployee, the new derived class also inherits grossSales and commissionRate as protected members.

Class BasePlusCommissionEmployee does not inherit class CommissionEmployee’s constructor. However, class BasePlusCommissionEmployee’s constructor (lines 12–19) calls class CommissionEmployee’s constructor with a constructor initializer. Again, BasePlusCommissionEmployee’s constructor must explicitly call CommissionEmployee’s constructor, because CommissionEmployee does not provide a parameterless constructor that could be invoked implicitly.

Fig. 11.9 BasePlusCommissionEmployee inherits from CommissionEmployee and has access to CommissionEmployee's protected members.

Alternate View

  1   // Fig.  11.9: BasePlusCommissionEmployee.cs
  2   // BasePlusCommissionEmployee inherits from CommissionEmployee and has
  3   // access to CommissionEmployee's protected members.
  4   using System;
  5
  6   public class BasePlusCommissionEmployee : CommissionEmployee
  7   {
  8      private decimal baseSalary; // base salary per week
  9
 10     // six-parameter derived-class constructor
 11     // with call to base class CommissionEmployee constructor
 12     public BasePlusCommissionEmployee(string firstName, string lastName,
 13         string socialSecurityNumber, decimal grossSales,
 14         decimal commissionRate, decimal baseSalary)
 15         : base(firstName, lastName, socialSecurityNumber,
 16            grossSales, commissionRate)                                   
 17     {
 18         BaseSalary = baseSalary; // validates base salary
 19     }
 20
 21     // property that gets and sets
 22     // BasePlusCommissionEmployee's base salary
 23     public decimal BaseSalary
 24     {
 25        get
 26        {
 27           return baseSalary;
 28         }
 29         set
 30         {
 31           if (value < 0) // validation
 32           {
 33              throw new ArgumentOutOfRangeException(nameof(value),
 34                 value, $"{nameof(BaseSalary)} must be >= 0");
 35            }
 36
 37            baseSalary = value;
 38         }
 39      }
 40
 41     // calculate earnings
 42     public override decimal Earnings() =>        
 43         baseSalary + (commissionRate * grossSales);
 44
 45     // return string representation of BasePlusCommissionEmployee
 46     public override string ToString() =>
 47        $"base-salaried commission employee: {FirstName} {LastName}
" +
 48        $"social security number: {SocialSecurityNumber}n" +
 49           $"gross sales: {grossSales:C}
" +
 50           $"commission rate: {commissionRate:F2}
" +
 51           $"base salary: {baseSalary}";
 52     }

Testing Class BasePlusCommissionEmployee

The BasePlusCommissionEmployeeTest class in this example’s project is identical to that of Fig. 11.7 and produces the same output, so we do not show the code here. Although the version of class BasePlusCommissionEmployee in Fig. 11.6 does not use inheritance and the version in Fig. 11.9 does, both classes provide the same functionality. The source code in Fig. 11.9 (52 lines) is considerably shorter than that in Fig. 11.6 (97 lines), because a large portion of the class’s functionality is now inherited from CommissionEmployee—there’s now only one copy of the CommissionEmployee functionality. This makes the code easier to maintain, modify and debug, because the code related to a CommissionEmployee exists only in that class.

public vs. protected Data

We could have declared base class CommissionEmployee’s instance variables grossSales and commissionRate as public to enable derived class BasePlusCommissionEmployee to access the base-class instance variables. However, declaring public instance variables is poor software engineering, because it allows unrestricted access to the instance variables by any of the class’s clients, greatly increasing the chance of errors and inconsistencies. With protected instance variables, the derived class gets access to the instance variables, but classes that are not derived from the base class cannot access its variables directly.

Problems with protected Instance Variables

In this example, we declared base-class instance variables as protected so that derived classes could access them. Inheriting protected instance variables enables you to directly access the variables in the derived class without invoking the set or get accessors of the corresponding property, thus violating encapsulation. In most cases, it’s better to use private instance variables and access them via properties to encourage proper software engineering. Your code will be easier to maintain, modify and debug.

Using protected instance variables creates several potential problems. First, since the derived-class object can set an inherited variable’s value directly without using a property’s set accessor, a derived-class object can assign an invalid value to the variable. For example, if we were to declare CommissionEmployee’s instance variable grossSales as protected, a derived-class object (e.g., BasePlusCommissionEmployee) could then directly assign a negative value to grossSales, making it invalid.

The second problem with protected instance variables is that derived-class methods are more likely to be written to depend on the base class’s data implementation. In practice, derived classes should depend only on the base-class services (i.e., non-private methods and properties) and not on the base-class data implementation. With protected instance variables in the base class, we may need to modify all the derived classes of the base class if the base-class implementation changes. For example, if for some reason we were to change the names of instance variables grossSales and commissionRate, then we’d have to do so for all occurrences in which a derived class directly references base-class instance variables grossSales and commissionRate. In such a case, the software is said to be fragile or brittle, because a small change in the base class can “break” derived-class implementation. You should be able to change the base-class implementation while still providing the same services to the derived classes. Of course, if the base-class services change, we must reimplement our derived classes.

Software Engineering Observation 11.4

Declaring base-class instance variables private (as opposed to protected) enables the base-class implementation of these instance variables to change without affecting derived-class implementations.

11.4.5 CommissionEmployeeBasePlusCommissionEmployee Inheritance Hierarchy Using private Instance Variables

We now reexamine our hierarchy, this time using the best software engineering practices.

Base Class CommissionEmployee

Class CommissionEmployee (Fig. 11.10) once again declares instance variables grossSales and commissionRate as private (lines 10–11). Methods Earnings (line 65) and To-String (lines 68–72) no longer directly access these instance variables—rather they use properties GrossSales and CommissionRate to access the data. If we decide to change the instance-variable names, the Earnings and ToString declarations will not require modification—only the bodies of the properties GrossSales and CommissionRate that directly manipulate the instance variables will need to change. These changes occur solely within the base class—no changes to the derived class are needed. Localizing the effects of changes like this is a good software engineering practice. Derived class BasePlusCommissionEmployee (Fig. 11.11) inherits from CommissionEmployee’s and can access the private base-class members via the inherited public properties.

Fig. 11.10 CommissionEmployee class represents a commission employee.

Alternate View

  1   // Fig. 11.10: CommissionEmployee.cs
  2   // CommissionEmployee class represents a commission employee.
  3   using System;
  4
  5   public class CommissionEmployee
  6   {
  7      public string FirstName { get; }
  8      public string LastName { get; }
  9      public string SocialSecurityNumber { get; }
 10      private decimal grossSales; // gross weekly sales       
 11      private decimal commissionRate; // commission percentage
 12
 13      // five-parameter constructor
 14      public CommissionEmployee(string firstName, string lastName,
 15         string socialSecurityNumber, decimal grossSales,
 16         decimal commissionRate)
 17      {
 18         // implicit call to object constructor occurs here
 19         FirstName = firstName;
 20         LastName = lastName;
 21         SocialSecurityNumber = socialSecurityNumber;
 22         GrossSales = grossSales; // validates gross sales
 23         CommissionRate = commissionRate; // validates commission rate
 24      }
 25
 26      // property that gets and sets commission employee's gross sales
 27      public decimal GrossSales
 28      {
 29         get
 30         {
 31            return grossSales;
 32         }
 33         set
 34         {
 35            if (value < 0) // validation
 36            {
 37               throw new ArgumentOutOfRangeException(nameof(value),
 38                  value, $" {nameof(GrossSales)} must be >= 0");
 39            }
 40
 41            grossSales = value;
 42         }
 43      }
 44
 45      // property that gets and sets commission employee's commission rate
 46      public decimal CommissionRate
 47      {
 48         get
 49         {
 50            return commissionRate;
 51         }
 52         set
 53         {
 54            if (value <= 0 || value >= 1) // validation
 55            {
 56               throw new ArgumentOutOfRangeException(nameof(value),
 57                  value, $" {nameof(CommissionRate)} must be > 0 and < 1");
 58            }
 59
 60            commissionRate = value;
 61         }
 62      }
 63
 64      // calculate commission employee's pay
 65      public virtual decimal Earnings() => CommissionRate * GrossSales;
 66
 67      // return string representation of CommissionEmployee object
 68      public override string ToString() =>
 69         $" commission employee: {FirstName} {LastName}
"  +
 70         $" social security number: {SocialSecurityNumber}
"  +
 71         $" gross sales: {GrossSales:C}
"  +
 72         $" commission rate: {CommissionRate:F2}" ;
 73   }

Derived Class BasePlusCommissionEmployee

Class BasePlusCommissionEmployee (Fig. 11.11) has several changes to its method implementations that distinguish it from the version in Fig. 11.9. Methods Earnings (Fig. 11.11, line 43) and ToString (lines 46–47) each invoke property BaseSalary’s get accessor to obtain the base-salary value, rather than accessing baseSalary directly. If we decide to rename instance variable baseSalary, only the body of property BaseSalary will need to change.

Fig. 11.11 BasePlusCommissionEmployee inherits from CommissionEmployee and has access to CommissionEmployee's private data via its public properties.

Alternate View

  1   // Fig. 11.11: BasePlusCommissionEmployee.cs
  2   // BasePlusCommissionEmployee inherits from CommissionEmployee and has
  3   // controlled access to CommissionEmployee's private data via
  4   // its public properties.
  5   using System;
  6
  7   public class BasePlusCommissionEmployee : CommissionEmployee
  8   {
  9      private decimal baseSalary; // base salary per week
 10
 11      // six-parameter derived-class constructor
 12      // with call to base class CommissionEmployee constructor
 13      public BasePlusCommissionEmployee(string firstName, string lastName,
 14         string socialSecurityNumber, decimal grossSales,
 15         decimal commissionRate, decimal baseSalary)
 16         : base(firstName, lastName, socialSecurityNumber,
 17              grossSales, commissionRate)
 18      {
 19         BaseSalary = baseSalary; // validates base salary
 20      }
 21
 22      // property that gets and sets
 23      // BasePlusCommissionEmployee's base salary
 24      public decimal BaseSalary
 25      {
 26         get
 27         {
 28             return baseSalary;
 29         }
 30         set
 31         {
 32             if (value < 0) // validation
 33             {
 34                 throw new ArgumentOutOfRangeException(nameof(value),
 35                    value, $"{nameof(BaseSalary)} must be >= 0");
 36             }
 37
 38             baseSalary = value;
 39          }
 40       }
 41
 42       // calculate earnings
 43       public override decimal Earnings() => BaseSalary + base.Earnings();
 44
 45       // return string representation of BasePlusCommissionEmployee
 46       public override string ToString() =>
 47          $"base-salaried {base.ToString()}
base salary: {BaseSalary:C}";
 48    }

BasePlusCommissionEmployee Method Earnings

Class BasePlusCommissionEmployee’s Earnings method (Fig. 11.11, line 43) overrides class CommissionEmployee’s Earnings method (Fig. 11.10, line 65) to calculate the earnings of a BasePlusCommissionEmployee. The new version obtains the portion of the employee’s earnings based on commission alone by calling CommissionEmployee’s Earnings method with the expression base.Earnings() (Fig. 11.11, line 43), then adds the base salary to this value to calculate the total earnings of the employee. Note the syntax used to invoke an overridden base-class method from a derived class—place the keyword base and the member access operator (.) before the base-class method name. This method invocation is a good software engineering practice—by having BasePlusCommissionEmployee’s Earnings method invoke CommissionEmployee’s Earnings method to calculate part of a BasePlusCommissionEmployee object’s earnings, we avoid duplicating the code and reduce code-maintenance problems.

Common Programming Error 11.3

When a base-class method is overridden in a derived class, the derived-class version often calls the base-class version to do a portion of the work. Failure to prefix the base-class method name with the keyword base and the member access (.) operator when referencing the base class’s method from the derived-class version causes the derived-class method to call itself, creating infinite recursion.

BasePlusCommissionEmployee Method ToString

Similarly, BasePlusCommissionEmployee’s ToString method (Fig. 11.11, lines 46–47) overrides CommissionEmployee’s (Fig. 11.10, lines 68–72) to return a string representation that’s appropriate for a base-salaried commission employee. The new version creates part of BasePlusCommissionEmployee string representation (i.e., the string "commission employee" and the values of CommissionEmployee’s data) by calling CommissionEmployee’s ToString method with the expression base.ToString() (Fig. 11.11, line 47) and incorporating the result into the string returned by the derived class’s ToString method, which includes the base salary.

Testing Class BasePlusCommissionEmployee

Class BasePlusCommissionEmployeeTest performs the same manipulations on a BasePlusCommissionEmployee object as in Figs. 11.7 and produces the same output, so we do not show it here. Although each BasePlusCommissionEmployee class you’ve seen behaves identically, the version in Fig. 11.11 is the best engineered. By using inheritance and by using properties that hide the data and ensure consistency, we have efficiently and effectively constructed a well-engineered class.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset