2. When the app executes, another compiler (known as the just-in-time compiler
12.7 Case Study: Creating and Using Interfaces
Our next example (Figs. 12.11–12.15) reexamines the payroll system of Section 12.5.
Suppose that the company involved wishes to perform several accounting operations in a single accounts-payable app—in addition to calculating the payroll earnings that must be paid to each employee, the company must also calculate the payment due on each of sev- eral invoices (i.e., bills for goods purchased). Though applied to unrelated things (i.e., em- ployees and invoices), both operations have to do with calculating some kind of payment amount. For an employee, the payment refers to the employee’s earnings. For an invoice, the payment refers to the total cost of the goods listed on the invoice. Can we calculate such different things as the payments due for employees and invoices polymorphically in a single app? Is there a capability that requires thatunrelatedclasses implement a set of common methods (e.g., a method that calculates a payment amount)?Interfacesoffer ex- actly this capability.
Performance Tip 12.1
The compiler can decide to inline asealedmethod call and will do so for small, simple
sealedmethods. Inlining does not violate encapsulation or information hiding, but does improve performance, because it eliminates the overhead of making a method call.
Common Programming Error 12.4
Attempting to declare a derived class of asealedclass is a compilation error.
12.7 Case Study: Creating and Using Interfaces 467
Standardized Interactions
Interfaces define and standardize the ways in which people and systems can interact with one another. For example, the controls on a radio serve as an interface between a radio’s users and its internal components. The controls allow users to perform a limited set of op- erations (e.g., changing the station, adjusting the volume, choosing between AM and FM), and different radios may implement the controls in different ways (e.g., using push but- tons, dials, voice commands). The interface specifieswhatoperations a radio must permit users to perform but does not specifyhowthey’re performed. Similarly, the interface be- tween a driver and a car with a manual transmission includes the steering wheel, the gear shift, the clutch pedal, the gas pedal and the brake pedal. This same interface is found in nearly all manual-transmission cars, enabling someone who knows how to drive one par- ticular manual-transmission car to drive just about any other. The components of each car may look a bit different, but thegeneralpurpose is the same—to allow people to drive the car.
Interfaces in Software
Software objects also communicate via interfaces. A C# interface describes a set of methods and properties that can be called on an object—to tell it, for example, to perform some task or return some piece of information. The next example introduces an interface namedIP-
ayablethat describes the functionality of any object that must be capable of being paid and thus must offer a method to determine the proper payment amount due. Aninterface dec- larationbegins with the keywordinterfaceand can contain only abstract methods, ab- stract properties, abstract indexers (not covered in this book) and abstract events (events are discussed in Chapter 14, Graphical User Interfaces with Windows Forms: Part 1.) All in- terface members areimplicitlydeclared bothpublicandabstract. In addition, each inter- face can extend one or more other interfaces to create a more elaborate interface that other classes can implement.
Implementing an Interface
To use an interface, a class must specify that itimplementsthe interface by listing the in- terface after the colon (:) in the class declaration. This is thesamesyntax used to indicate inheritance from a base class. A concrete class implementing the interface must declare each member of the interface with the signature specified in the interface declaration. A class that implements an interface but doesnotimplement all its members is an abstract class—it must be declaredabstractand must contain anabstractdeclaration for each unimplemented member of the interface. Implementing an interface is like signing a con- tract with the compiler that states, “I will provide an implementation for all the members specified by the interface, or I will declare themabstract.”
Common Programming Error 12.5
It’s a compilation error to declare an interface memberpublicorabstractexplicitly, be- cause they’re redundant in interface-member declarations. It’s also a compilation error to specify any implementation details, such as concrete method declarations, in an interface.
Common Programming Error 12.6
Failing to define or declare any member of an interface in a class that implements the in- terface results in a compilation error.
Common Methods for Unrelated Classes
An interface is typically used whenunrelatedclasses need tosharecommon methods. This allows objects of unrelated classes to be processed polymorphically—objects of classes that implement the same interface can respond to thesamemethod calls. You can create an in- terface that describes the desired functionality, then implement this interface in any classes requiring that functionality. For example, in the accounts-payable app developed in this section, we implement interfaceIPayablein any class that must be able to calculate a pay- ment amount (e.g.,Employee,Invoice).
Interfaces vs. Abstract Classes
An interface often is used in place of anabstractclass when there’s no default implemen- tation to inherit—that is, no fields and no default method implementations. Like ab-
stractclasses, interfaces are typicallypublictypes, so they’re normally declared in files by themselves with the same name as the interface and the.csfile-name extension.
12.7.1 Developing anIPayableHierarchy
To build an app that can determine payments for employees and invoices alike, we first create an interface namedIPayable. InterfaceIPayablecontains methodGetPayment- Amountthat returns adecimalamount to be paid for an object of any class that imple- ments the interface. MethodGetPaymentAmountis a general-purpose version of method
Earningsof the Employeehierarchy—method Earningscalculates a payment amount specifically for anEmployee, whileGetPaymentAmountcan be applied to a broad range of unrelatedobjects. After declaring interfaceIPayable, we introduce classInvoice, which implements interfaceIPayable. We then modify classEmployeesuch that it also imple- ments interfaceIPayable. Finally, we updateEmployeederived classSalariedEmployee
to “fit” into theIPayablehierarchy (i.e., we renameSalariedEmployeemethodEarnings asGetPaymentAmount).
ClassesInvoiceandEmployeeboth represent things for which the company must be able to calculate a payment amount. Both classes implement IPayable, so an app can invoke methodGetPaymentAmountonInvoiceobjects andEmployeeobjects alike. This enables the polymorphic processing of Invoices and Employees required for our com- pany’s accounts-payable app.
UML Diagram Containing an Interface
The UML class diagram in Fig. 12.10 shows the interface and class hierarchy used in our accounts-payable app. The hierarchy begins with interfaceIPayable. The UML distin- guishes an interface from a class by placing the word “interface” in guillemets (ô and ằ)
Good Programming Practice 12.1
By convention, the name of an interface begins withI. This helps distinguish interfaces from classes, improving code readability.
Good Programming Practice 12.2
When declaring a method in an interface, choose a name that describes the method’s pur- pose in a general manner, because the method may be implemented by a broad range of unrelated classes.
12.7 Case Study: Creating and Using Interfaces 469
above the interface name. The UML expresses the relationship between a class and an in- terface through arealization. A class is said to “realize,” or implement, an interface. A class diagram models a realization as a dashed arrow with ahollow arrowheadpointing from the implementing class to the interface. The diagram in Fig. 12.10 indicates that classesIn-
voiceandEmployeeeach realize (i.e., implement) interfaceIPayable. As in the class dia- gram of Fig. 12.2, classEmployeeappears in italics, indicating that it’s an abstract class.
Concrete classSalariedEmployeeextendsEmployeeand inherits its base class’s realization relationship with interfaceIPayable.
12.7.2 Declaring InterfaceIPayable
The declaration of interfaceIPayablebegins in Fig. 12.11 at line 3. Interface IPayable
containspublic abstractmethodGetPaymentAmount(line 5). The method cannot beex- plicitlydeclaredpublicorabstract. Interfaces can haveanynumber of members and in- terface methods can have parameters.
12.7.3 Creating ClassInvoice
We now create classInvoice(Fig. 12.12) to represent a simple invoice that contains bill- ing information for one kind of part. The class contains propertiesPartNumber(line 11),
PartDescription(line 14),Quantity(lines 27–41) andPricePerItem(lines 44–58) that indicate the part number, the description of the part, the quantity of the part ordered and the price per item. ClassInvoicealso contains a constructor (lines 17–24) and aToString method (lines 61–67) that returns a string representation of anInvoiceobject. Theset accessors of propertiesQuantityandPricePerItemensure thatquantityandpricePer-
Itemare assigned only nonnegative values.
Fig. 12.10 | IPayableinterface and class hierarchy UML class diagram.
1 // Fig. 12.11: IPayable.cs
2 // IPayable interface declaration.
3 public interface IPayable 4 {
5 decimal GetPaymentAmount(); // calculate payment; no implementation 6 } // end interface IPayable
Fig. 12.11 | IPayableinterface declaration.
Invoice Employee
SalariedEmployee
ôinterfaceằ
IPayable
1 // Fig. 12.12: Invoice.cs
2 // Invoice class implements IPayable.
3 using System;
4 5 6 {
7 private int quantity;
8 private decimal pricePerItem;
9
10 // property that gets and sets the part number on the invoice 11 public string PartNumber { get; set; }
12
13 // property that gets and sets the part description on the invoice 14 public string PartDescription { get; set; }
15
16 // four-parameter constructor
17 public Invoice( string part, string description, int count, 18 decimal price )
19 {
20 PartNumber = part;
21 PartDescription = description;
22 Quantity = count; // validate quantity via property
23 PricePerItem = price; // validate price per item via property 24 } // end four-parameter Invoice constructor
25
26 // property that gets and sets the quantity on the invoice 27 public int Quantity
28 {
29 get
30 {
31 return quantity;
32 } // end get
33 set
34 {
35 if ( value >= 0 ) // validate quantity
36 quantity = value;
37 else
38 throw new ArgumentOutOfRangeException( "Quantity", 39 value, "Quantity must be >= 0" );
40 } // end set
41 } // end property Quantity 42
43 // property that gets and sets the price per item 44 public decimal PricePerItem
45 {
46 get
47 {
48 return pricePerItem;
49 } // end get
50 set
51 {
52 if ( value >= 0 ) // validate price
53 quantity = value;
Fig. 12.12 | Invoiceclass implementsIPayable. (Part 1 of 2.)
public class Invoice : IPayable
12.7 Case Study: Creating and Using Interfaces 471
Line 5 indicates that classInvoiceimplements interfaceIPayable. Like all classes, classInvoicealso implicitly inherits from classobject.C# does not allow derived classes to inherit from more than one base class, but it does allow a class to inherit from a base class and implementanynumber of interfaces. All objects of a class that implement multiple interfaces have theis-arelationship with each implemented interface type. To implement more than one interface, use a comma-separated list of interface names after the colon (:) in the class declaration, as in:
When a class inherits from a base class and implements one or more interfaces, the class declarationmustlist the base-class namebeforeany interface names.
Class Invoice implements the one method in interface IPayable—method
GetPaymentAmountis declared in lines 70–73. The method calculates the amount required to pay the invoice. The method multiplies the values ofquantity and pricePerItem
(obtained through the appropriate properties) and returns the result (line 72). This method satisfies the implementation requirement for the method in interfaceIPayable— we’ve fulfilled theinterface contractwith the compiler.
12.7.4 Modifying ClassEmployeeto Implement InterfaceIPayable We now modify classEmployeeto implement interfaceIPayable. Figure 12.13 contains the modifiedEmployeeclass. This class declaration is identical to that of Fig. 12.4 with two ex- ceptions. First, line 3 of Fig. 12.13 indicates that classEmployeenow implements interface
IPayable. Because of this, we renamedEarningstoGetPaymentAmountthroughout theEm-
ployeehierarchy. As with methodEarningsin Fig. 12.4, however, it does not make sense to implement methodGetPaymentAmountin classEmployee, because we cannot calculate the
54 else
55 throw new ArgumentOutOfRangeException( "PricePerItem", 56 value, "PricePerItem must be >= 0" );
57 } // end set
58 } // end property PricePerItem 59
60 // return string representation of Invoice object 61 public override string ToString()
62 {
63 return string.Format(
64 "{0}: \n{1}: {2} ({3}) \n{4}: {5} \n{6}: {7:C}", 65 "invoice", "part number", PartNumber, PartDescription, 66 "quantity", Quantity, "price per item", PricePerItem );
67 } // end method ToString 68
69 // method required to carry out contract with interface IPayable 70
71 72 73
74 } // end class Invoice
public class ClassName : BaseClassName, FirstInterface, SecondInterface, … Fig. 12.12 | Invoiceclass implementsIPayable. (Part 2 of 2.)
public decimal GetPaymentAmount() {
return Quantity * PricePerItem; // calculate total cost } // end method GetPaymentAmount
earnings payment owed to ageneralEmployee—first, we must know thespecifictype ofEm-
ployee. In Fig. 12.4, we declared methodEarningsasabstractfor this reason, and as a re- sult, classEmployeehad to be declaredabstract. This forced eachEmployeederived class to overrideEarningswith a concrete implementation. [Note:Though we renamedEarnings toGetPaymentAmount, in classEmployeewe could have definedGetPaymentAmountand had it callEarnings. Then the otherEmployeehierarchy classes would not need to change.]
In Fig. 12.13, we handle this situation the same way. Recall that when a class imple- ments an interface, the class makes a contract with the compiler stating that the class either will implementeachof the methods in the interfaceorwill declare themabstract. If the latter option is chosen, we must also declare the class abstract. As we discussed in Section 12.4, any concrete derived class of the abstract class must implement theabstract methods of the base class. If the derived class does not do so, it too must be declared
abstract. As indicated by the comments in lines 29–30 of Fig. 12.13, classEmployeedoes not implement methodGetPaymentAmount, so the class is declaredabstract.
1 // Fig. 12.13: 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 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 30 31
32 } // end abstract class Employee
Fig. 12.13 | Employeeabstract base class.
public abstract class Employee : IPayable
// Note: We do not implement IPayable method GetPaymentAmount here, so // this class must be declared abstract to avoid a compilation error.
public abstract decimal GetPaymentAmount();
12.7 Case Study: Creating and Using Interfaces 473
12.7.5 Modifying ClassSalariedEmployeefor Use withIPayable Figure 12.14 contains a modified version of classSalariedEmployeethat extendsEmployee and implements methodGetPaymentAmount. This version ofSalariedEmployeeis identical to that of Fig. 12.5 with the exception that the version here implements methodGetPay-
mentAmount(lines 35–38) instead of methodEarnings. The two methods contain thesame functionality but have different names. Recall that theIPayableversion of the method has a more general name to be applicable to possibly disparate classes. The remainingEmployee derived classes (e.g.,HourlyEmployee,CommissionEmployeeandBasePlusCommissionEm- ployee) also must be modified to contain methodGetPaymentAmountin place ofEarnings
to reflect the fact thatEmployeenow implementsIPayable. We leave these modifications as an exercise and use onlySalariedEmployeein our test app in this section.
1 // Fig. 12.14: SalariedEmployee.cs
2 // SalariedEmployee class that extends Employee.
3 using System;
4
5 public class SalariedEmployee : Employee 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; implement interface IPayable method 34 // that was abstract in base class Employee
35 36 37 38
Fig. 12.14 | SalariedEmployeeclass that extendsEmployee. (Part 1 of 2.)
public override decimal GetPaymentAmount() {
return WeeklySalary;
} // end method GetPaymentAmount
When a class implements an interface, the sameis-arelationship provided by inheri- tance applies. ClassEmployeeimplementsIPayable, so we can say that anEmployeeis an
IPayable, as are any classes that extendEmployee. As such,SalariedEmployeeobjects are
IPayableobjects. An object of a class that implements an interface may be thought of as an object of the interface type. Objects of any classes derived from the class that imple- ments the interface can also be thought of as objects of the interface type. Thus, just as we can assign the reference of aSalariedEmployeeobject to a base-classEmployeevariable, we can assign the reference of aSalariedEmployeeobject to an interfaceIPayablevari- able.InvoiceimplementsIPayable, so anInvoiceobject alsois anIPayableobject, and we can assign the reference of anInvoiceobject to anIPayablevariable.
12.7.6 Using InterfaceIPayableto ProcessInvoices andEmployees Polymorphically
PayableInterfaceTest(Fig. 12.15) illustrates that interfaceIPayablecan be used to pro- cess a set ofInvoices andEmployees polymorphically in a single app. Line 10 declares
payableObjectsand assigns it an array of fourIPayablevariables. Lines 13–14 assign the references ofInvoiceobjects to the first two elements ofpayableObjects. Lines 15–18 assign the references of SalariedEmployee objects to the remaining two elements of
payableObjects. These assignments are allowed because anInvoice is anIPayable, a
SalariedEmployeeis anEmployeeand anEmployeeis anIPayable. Lines 24–29 use a
foreachstatement to process eachIPayableobject inpayableObjectspolymorphically, displaying the object as astring, along with the payment due. Lines 27–28 implicitly in- vokes methodToStringoff anIPayableinterface reference, even thoughToStringis not declared in interfaceIPayable—all references (including those of interface types) refer to 39
40 // return string representation of SalariedEmployee object 41 public override string ToString()
42 {
43 return string.Format( "salaried employee: {0}\n{1}: {2:C}", 44 base.ToString(), "weekly salary", WeeklySalary );
45 } // end method ToString 46 } // end class SalariedEmployee
Software Engineering Observation 12.4
Inheritance and interfaces are similar in their implementation of theis-arelationship. An object of a class that implements an interface may be thought of as an object of that interface type. An object of any derived classes of a class that implements an interface also can be thought of as an object of the interface type.
Software Engineering Observation 12.5
Theis-a relationship that exists between base classes and derived classes, and between interfaces and the classes that implement them, holds when passing an object to a method.
When a method parameter receives an argument of a base class or interface type, the method polymorphically processes the object received as an argument.
Fig. 12.14 | SalariedEmployeeclass that extendsEmployee. (Part 2 of 2.)
12.7 Case Study: Creating and Using Interfaces 475
objects that extendobjectand therefore have aToStringmethod. Line 28 invokesIPay-
ablemethodGetPaymentAmountto obtain the payment amount for each object inpay-
ableObjects, regardless of the actual type of the object. The output reveals that the method calls in lines 27–28 invoke the appropriate class’s implementation of methodsTo-
StringandGetPaymentAmount. For instance, whencurrentPayablerefers to anInvoice during the first iteration of theforeachloop, classInvoice’sToStringandGetPaymen-
tAmountmethods execute.
Software Engineering Observation 12.6
All methods of classobjectcan be called by using a reference of an interface type—the reference refers to an object, and all objects inherit the methods of classobject.
1 // Fig. 12.15: PayableInterfaceTest.cs
2 // Tests interface IPayable with disparate classes.
3 using System;
4
5 public class PayableInterfaceTest 6 {
7 public static void Main( string[] args )
8 {
9 // create four-element IPayable array
10 IPayable[] payableObjects = new IPayable[ 4 ];
11
12 // populate array with objects that implement IPayable
13 payableObjects[ 0 ] = new Invoice( "01234", "seat", 2, 375.00M );
14 payableObjects[ 1 ] = new Invoice( "56789", "tire", 4, 79.95M );
15 payableObjects[ 2 ] = new SalariedEmployee( "John", "Smith", 16 "111-11-1111", 800.00M );
17 payableObjects[ 3 ] = new SalariedEmployee( "Lisa", "Barnes", 18 "888-88-8888", 1200.00M );
19
20 Console.WriteLine(
21 "Invoices and Employees processed polymorphically:\n" );
22
23 // generically process each element in array payableObjects 24 foreach ( var currentPayable in payableObjects )
25 {
26 // output currentPayable and its appropriate payment amount 27 Console.WriteLine( "{0}\npayment due: {1:C}\n",
28 currentPayable, currentPayable.GetPaymentAmount() );
29 } // end foreach 30 } // end Main
31 } // end class PayableInterfaceTest
Invoices and Employees processed polymorphically:
invoice:
part number: 01234 (seat) quantity: 2
price per item: $375.00 payment due: $750.00
Fig. 12.15 | Tests interfaceIPayablewith disparate classes. (Part 1 of 2.)