Let’s reexamine our hierarchy once more, this time using good software engineering practices. Class CommissionEmployee
(Fig. G.10) declares instance variables firstName
, lastName
, socialSecurityNumber
, grossSales
and commissionRate
as private (lines 6–10) and provides public
methods setFirstName
, getFirstName
, setLastName
, getLastName
, setSocialSecurityNumber
, getSocialSecurityNumber
, setGrossSales
, getGrossSales
, setCommissionRate
, getCommissionRate
, earnings
and toString
for manipulating these values. Methods earnings
(lines 93–96) and toString
(lines 99–107) use the class’s get methods to obtain the values of its instance variables. If we decide to change the instance-variable names, the earnings
and toString
declarations will not require modification—only the bodies of the get and set methods that directly manipulate the instance variables will need to change. These changes occur solely within the superclass—no changes to the subclass are needed. Localizing the effects of changes like this is a good software engineering practice.
1 // Fig. G.10: CommissionEmployee.java
2 // CommissionEmployee class uses methods to manipulate its
3 // private instance variables.
4 public class CommissionEmployee
5 {
6 private String firstName;
7 private String lastName;
8 private String socialSecurityNumber;
9 private double grossSales; // gross weekly sales
10 private double commissionRate; // commission percentage
11
12 // five-argument constructor
13 public CommissionEmployee( String first, String last, String ssn,
14 double sales, double rate )
15 {
16 // implicit call to Object constructor occurs here
17 firstName = first;
18 lastName = last;
19 socialSecurityNumber = ssn;
20 setGrossSales( sales ); // validate and store gross sales
21 setCommissionRate( rate ); // validate and store commission rate
22 } // end five-argument CommissionEmployee constructor
23
24 // set first name
25 public void setFirstName( String first )
26 {
27 firstName = first; // should validate
28 } // end method setFirstName
29
30 // return first name
31 public String getFirstName()
32 {
33 return firstName;
34 } // end method getFirstName
35
36 // set last name
37 public void setLastName( String last )
38 {
39 la5stName = last; // should validate
40 } // end method setLastName
41
42 // return last name
43 public String getLastName()
44 {
45 return lastName;
46 } // end method getLastName
47
48 // set social security number
49 public void setSocialSecurityNumber( String ssn )
50 {
51 socialSecurityNumber = ssn; // should validate
52 } // end method setSocialSecurityNumber
53
54 // return social security number
55 public String getSocialSecurityNumber()
56 {
57 return socialSecurityNumber;
58 } // end method getSocialSecurityNumber
59
60 // set gross sales amount
61 public void setGrossSales( double sales )
62 {
63 if ( sales >= 0.0 )
64 grossSales = sales;
65 else
66 throw new IllegalArgumentException(
67 "Gross sales must be >= 0.0" );
68 } // end method setGrossSales
69
70 // return gross sales amount
71 public double getGrossSales()
72 {
73 return grossSales;
74 } // end method getGrossSales
75
76 // set commission rate
77 public void setCommissionRate( double rate )
78 {
79 if ( rate > 0.0 && rate < 1.0 )
80 commissionRate = rate;
81 else
82 throw new IllegalArgumentException(
83 "Commission rate must be > 0.0 and < 1.0" );
84 } // end method setCommissionRate
85
86 // return commission rate
87 public double getCommissionRate()
88 {
89 return commissionRate;
90 } // end method getCommissionRate
91
92 // calculate earnings
93 public double earnings()
94 {
95 return getCommissionRate() * getGrossSales();
96 } // end method earnings
97
98 // return String representation of CommissionEmployee object
99 @Override // indicates that this method overrides a superclass method
100 public String toString()
101 {
102 return String.format( "%s: %s %s
%s: %s
%s: %.2f
%s: %.2f",
103 "commission employee", getFirstName(), getLastName(),
104 "social security number", getSocialSecurityNumber(),
105 "gross sales", getGrossSales(),
106 "commission rate", getCommissionRate() );
107 } // end method toString
108 } // end class CommissionEmployee
Subclass BasePlusCommissionEmployee
(Fig. G.11) inherits CommissionEmployee
’s non-private
methods and can access the private
superclass members via those methods. Class BasePlusCommissionEmployee
has several changes that distinguish it from Fig. G.9. Methods earnings
(lines 35–39) and toString
(lines 42–47) each invoke method getBaseSalary
to obtain the base salary value, rather than accessing baseSalary
directly. If we decide to rename instance variable baseSalary
, only the bodies of method setBaseSalary
and getBaseSalary
will need to change.
1 // Fig. G.11: BasePlusCommissionEmployee.java
2 // BasePlusCommissionEmployee class inherits from CommissionEmployee
3 // and accesses the superclass's private data via inherited
4 // public methods.
5
6 public class BasePlusCommissionEmployee extends CommissionEmployee
7 {
8 private double baseSalary; // base salary per week
9
10 // six-argument constructor
11 public BasePlusCommissionEmployee( String first, String last,
12 String ssn, double sales, double rate, double salary )
13 {
14 super( first, last, ssn, sales, rate );
15 setBaseSalary( salary ); // validate and store base salary
16 } // end six-argument BasePlusCommissionEmployee constructor
17
18 // set base salary
19 public void setBaseSalary( double salary )
20 {
21 if ( salary >= 0.0 )
22 baseSalary = salary;
23 else
24 throw new IllegalArgumentException(
25 "Base salary must be >= 0.0" );
26 } // end method setBaseSalary
27
28 // return base salary
29 public double getBaseSalary()
30 {
31 return baseSalary;
32 } // end method getBaseSalary
33
34 // calculate earnings
35 @Override // indicates that this method overrides a superclass method
36 public double earnings()
37 {
38 return getBaseSalary() + super.earnings();
39 } // end method earnings
40
41 // return String representation of BasePlusCommissionEmployee
42 @Override // indicates that this method overrides a superclass method
43 public String toString()
44 {
45 return String.format( "%s %s
%s: %.2f", "base-salaried",
46 super.toString(), "base salary", getBaseSalary() );
47 } // end method toString
48 } // end class BasePlusCommissionEmployee
Method earnings
(lines 35–39) overrides class CommissionEmployee
’s earnings
method (Fig. G.10, lines 93–96) to calculate a base-salaried commission employee’s earnings. The new version obtains the portion of the earnings based on commission alone by calling CommissionEmployee
’s earnings
method with super.earnings()
(line 38), then adds the base salary to this value to calculate the total earnings. Note the syntax used to invoke an overridden superclass method from a subclass—place the keyword super
and a dot (.
) separator before the superclass method name. This method invocation is a good software engineering practice—if a method performs all or some of the actions needed by another method, call that method rather than duplicate its code. 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. If we did not use “super.
” then BasePlusCommissionEmployee
’s earnings
method would call itself rather than the superclass version. This would result in a phenomenon called infinite recursion, which would eventually cause the method-call stack to overflow—a fatal runtime error.
Similarly, BasePlusCommissionEmployee
’s toString
method (Fig. G.11, lines 42–47) overrides class CommissionEmployee
’s toString
method (Fig. G.10, lines 99–107) to return a String
representation that’s appropriate for a base-salaried commission employee. The new version creates part of a BasePlusCommissionEmployee
object’s String
representation (i.e., the String "commission employee"
and the values of class CommissionEmployee
’s private
instance variables) by calling CommissionEmployee
’s toString
method with the expression super.toString()
(Fig. G.11, line 46). BasePlusCommissionEmployee
’s toString
method then outputs the remainder of a BasePlusCommissionEmployee
object’s String
representation (i.e., the value of class BasePlusCommissionEmployee
’s base salary).
Class BasePlusCommissionEmployeeTest
performs the same manipulations on a BasePlusCommissionEmployee
object as in Fig. G.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. G.11 is the best engineered. By using inheritance and by calling methods that hide the data and ensure consistency, we’ve efficiently and effectively constructed a well-engineered class.