11.7. Case Study: Creating and Using InterfacesOur next example reexamines the payroll system of Section 11.5. Suppose that the company involved wishes to perform several accounting operations in an accounts payable application In addition to calculating the earnings that must be paid to each employee, the company also wants to calculate the payment due on each of several invoices (i.e., bills for goods and services purchased). Though applied to unrelated things (i.e., employees and invoices), both operations have to do with obtaining 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 and services listed on the invoice. Can we calculate such different things as the payments due for employees and invoices in a single application polymorphically? Does Visual Basic have a capability that forces possibly unrelated classes to implement a set of common methods (e.g., a method that calculates a payment amount)? Visual Basic interfaces offer exactly this capability. Interfaces define and standardize the ways in which things such as people and systems can interact with one another. For example, the controls on a radio serve as an interface between radio users and a radio's internal components. The controls allow users to perform only a limited set of operations (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 buttons, dials, voice commands). The interface specifies what operations a radio permits users to perform but does not specify how the operations are implemented. Similarly, the interface between a driver and a car with a manual transmission includes the steering wheel, the gear shift, and the clutch, gas and brake pedals. This same interface is found in nearly all manual transmission cars, enabling someone who knows how to drive one particular manual transmission car to drive just about any manual transmission car. The components of each individual car may look a bit different, but the components' general purpose is the sameto allow people to drive the car. Software objects also communicate via interfaces. A Visual Basic interface describes a set of methods that can be called on an object, to tell the object to perform some task or return some piece of information, for example. An interface is often used in place of a Must-Inherit class when there is no default implementation to inheritthat is, no instance variables and no default method and property implementations. The next example introduces an interface named IPayable to describe 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. An interface declaration begins with the keyword Interface and can contain abstract methods and properties, but cannot contain instance variables, concreate methods or concrete properties. Unlike classes, interfaces may not specify any implementation details, such as concrete method declarations and instance variables. All members declared in an interface are implicitly Public, and may not specify an access modifier. Common Programming Error 11.5
Common Programming Error 11.6
To use an interface, a concrete class must specify that it Implements the interface and must implement each method in the interface with the signature and return type specified in the interface declaration. A class that does not implement all the methods of the interface is an abstract class and must be declared MustInherit. Implementing an interface is like signing a contract with the compiler that states, "I will declare all the methods specified by the interface or I will declare my class MustInherit." Common Programming Error 11.7
An interface is typically used when unrelated classes need to share common methods and properties. This allows objects of unrelated classes to be processed polymorphically objects of classes that implement the same interface have an is-a relationship with the interface type and can respond to the same method calls. Programmers can create an interface that describes the desired functionality, then implement this interface in any classes that require that functionality. For example, in the accounts payable application that we develop in the next several subsections, we implement interface IPayable in any class that must be able to calculate a payment amount (e.g., Employee, Invoice). 11.7.1. Developing an IPayable HierarchyTo build an application that can determine payments for employees and invoices alike, we will first create interface IPayable. Interface IPayable contains method GetPayment-Amount, which returns a Decimal amount that must be paid for an IPayable object, and method ToString, which returns the String representation of an IPayable object. Method GetPaymentAmount is a general purpose version of the Employee hierarchy's CalculateEarnings methodmethod CalculateEarnings calculates a payment amount specifically for an Employee, while GetPaymentAmount can be applied more generally to unrelated objects. After declaring interface IPayable, we will introduce class Invoice, which implements interface IPayable. We then modify class Employee so that it, too, implements interface IPayable. Finally, we update Employee derived class SalariedEmployee to "fit" into the IPayable hierarchy (i.e., we rename SalariedEmployee method CalculateEarnings as GetPaymentAmount). Good Programming Practice 11.1
Classes Invoice and Employee both represent things for which the company must be able to calculate a payment amount. Both classes implement IPayable, so a program can invoke method GetPaymentAmount on Invoice objects and Employee objects alike. As you will soon see, this enables the polymorphic processing of Invoices and Employees required for our accounts payable application. The UML class diagram in Fig. 11.10 shows the class and interface hierarchy used in our accounts payable application. The hierarchy begins with interface IPayable. The UML distinguishes an interface from other classes by placing the word "interface" in guillemets (« ») above the interface name. The UML expresses the relationship between a class and an interface through an association known as a realization. A class is said to "realize," or implement, an interface. A class diagram models a realization as a dashed arrow with a hollow arrowhead pointing from the implementing classes to the interface. The diagram in Fig. 11.10 indicates that class Invoice realizes (i.e., implements) interface IPayable. Class Employee also realizes interface IPayable, but it is not required to provide method implementations, because it is an abstract class (note that it appears in italics). Concrete class SalariedEmployee inherits from Employee and inherits its base class's realization relationship with interface IPayable, so SalariedEmployee must implement the method(s) of IPayable. Figure 11.10. IPayable interface hierarchy UML class diagram.
11.7.2. Declaring Interface IPayableThe declaration of interface IPayable begins in Fig. 11.11 at line 3. Interface IPayable contains Public methods GetPaymentAmount (line 4) and ToString (line 5). Note that the methods are not explicitly declared Public. Interface methods are implicitly Public. Also note that we explicitly declare the ToString method. Unlike a class, an interface does not inherit from Object, so it does not implicitly have a ToString method. You must explicitly declare ToString in the interface if a program must call this method through an IPayable variable. Interfaces can have any number of methods. Although methods GetPaymentAmount and ToString have no parameters, interface methods can have parameters. Figure 11.11. IPayable interface declaration.
11.7.3. Creating Class InvoiceWe now create class Invoice (Fig. 11.12) to represent a simple invoice that contains billing information for only one kind of part. The class declares Private instance variables partNumberValue, partDescriptionValue, quantityValue and pricePerItemValue (in lines 69) that indicate the part number, a description of the part, the quantity of the part ordered and the price per item, respectively. Class Invoice also contains a constructor (lines 1218), and properties PartNumber (lines 2129), PartDescription(lines 3240), Quantity (lines 4355) and PricePerItem (lines 5870) that manipulate the class's instance variables. Note that the Set accessors of properties Quantity and PricePerItem ensure that quantityValue and pricePerItemValue are assigned only non-negative values. Figure 11.12. Invoice class that implements interface IPayable.
Line 4 of Fig. 11.12 indicates that class Invoice implements interface IPayable. Class Invoice also implicitly inherits from Object. A derived class cannot inherit from more than one base class, but can inherit from a base class and implement zero or more interfaces. To implement more than one interface, use a comma-separated list of interface names after keyword Implements in the class declaration, as in: Public Class ClassName Inherits BaseClassName Implements IFirstInterface, ISecondInterface, ... Inherits BaseClassName is optional if the class implicitly inherits from class Object. All objects of a class that implements multiple interfaces have the is-a relationship with each implemented interface type. Class Invoice implements the methods in interface IPayable. Method GetPaymentAmount is declared in lines 7376. Line 74 contains keyword Implements followed by IPayable.GetPaymentAmount to indicate that the method in lines 7376 is the implementation of the interface's GetPaymentAmount method. Line 75 calculates the total payment required to pay the invoice by multiplying the values of quantityValue and pricePerItemValue (obtained through the appropriate properties) and returning the result. Method ToString (lines 7985) returns the String representation of an Invoice object. Recall that all classes directly or indirectly inherit from class Object. Therefore, class Invoice implicitly inherits Object's ToString method. However, we want to declare a customized ToString method in Invoice that returns a String containing the values of an Invoice's instance variables. Line 79 indicates that Invoice's ToString method overrides the one defined in base class Object. We also must satisfy the requirements of the IPayable interface, which Invoice implements. Line 80 contains the keyword Implements followed by IPayable.ToString to indicate that this version of ToString also serves as the required implementation of IPayable's ToString method. Note that it is possible for the same method to both override a method of a base class and implement a method of an interface. Implementing methods GetPaymentAmount and ToString in class Invoice satisfies the implementation requirement for the methods of interface IPayableconcrete class Invoice fulfills the interface contract with the compiler. 11.7.4. Modifying Class Employee to Implement Interface IPayableWe now modify class Employee to implement interface IPayable. Figure 11.13 contains the modified Employee class. This class declaration is identical to that of Fig. 11.4 with only three exceptions. First, line 4 of Fig. 11.13 indicates that class Employee now implements interface IPayable. Second, we must implement method ToString (lines 5256) declared in the IPayable interface, which also overrides the version declared in base class Object. Third, since Employee now implements interface IPayable, we rename CalculateEarnings to GetPaymentAmount throughout the Employee hierarchy. As with method CalculateEarnings in the Employee class of Fig. 11.4, however, it does not make sense to implement method GetPaymentAmount in abstract class Employee because we cannot calculate the earnings payment owed to a general Employeefirst we must know the specific type of Employee. In Fig. 11.4, we declared method CalculateEarnings as MustOverride for this reason, so class Employee had to be declared MustInherit. This forced each concrete Employee derived class to override CalculateEarnings with an implementation. Figure 11.13. Employee class that implements interface IPayable.
In Fig. 11.13, we handle this situation similarly. Recall that when a class implements an interface, the class makes a contract with the compiler stating either that the class will implement each of the methods in the interface or that the class will be declared MustInherit. If you choose the latter option, you must declare the interface methods as Must-Override in the abstract class. Any concrete derived class of the abstract class must implement the interface methods to fulfill the base class's contract with the compiler. If the derived class does not do so, it too must be declared MustInherit. As indicated in lines 5960, class Employee of Fig. 11.13 does not implement method GetPaymentAmount, so the class is declared MustInherit (line 3). Each direct Employee derived class inherits the base class's contract to implement method GetPaymentAmount and thus must implement this method to become a concrete class from which objects can be instantiated. A class that inherits from one of Employee's concrete derived classes will inherit an implementation of GetPaymentAmount and thus will also be a concrete class. 11.7.5. Modifying Class SalariedEmployee for Use in the IPayable HierarchyFigure 11.14 contains a modified version of concrete class SalariedEmployee that inherits abstract class Employee and fulfills base class Employee's contract to implement interface IPayable's GetPaymentAmount method. This SalariedEmployee class is identical to that of Fig. 11.5, but this version implements method GetPaymentAmount (lines 3234) instead of method CalculateEarnings. These methods contain the same functionality but have different names. Recall that IPayable's GetPaymentAmount method has a more general name to be applicable to possibly disparate classes. The remaining Employee derived classes (e.g., HourlyEmployee, CommissionEmployee and BasePlusCommissionEmployee) also must be modified to contain GetPaymentAmount methods in place of CalculateEarnings methods to reflect the fact that Employee now implements IPayable. We use only SalariedEmployee in our test program in this section. Figure 11.14. SalariedEmployee class that implements interface IPayable methods GetPaymentAmount and ToString.
When a class implements an interface, the same is-a relationship as inheritance applies. For example, class Employee implements IPayable, so we can say that an Employee is an IPayable. Objects of any classes that inherit from Employee are also IPayable objects. SalariedEmployee objects, for instance, are IPayable objects. As with inheritance relationships, an object of a class that implements an interface may be thought of as an object of the interface type. Objects of any derived classes of the class that implements the interface can also be thought of as objects of the interface type. Thus, just as we can assign the reference of a SalariedEmployee object to a base class Employee variable, we can assign the reference of a SalariedEmployee object to an interface IPayable variable. Invoice implements IPayable, so an Invoice object also is an IPayable object, and we can assign the reference of an Invoice object to an IPayable variable. Software Engineering Observation 11.3
Software Engineering Observation 11.4
11.7.6. Using Interface IPayable to Process Invoices and Employees PolymorphicallyModule PayableInterfaceTest (Fig. 11.15) illustrates that interface IPayable can be used to process a set of Invoices and Employees polymorphically in a single application, even though these classes represent such different kinds of things. Figure 11.15. IPayable interface test program processing Invoices and Employees polymorphically.
Line 6 declares payableObjects and assigns to it an array of four IPayable variables. Lines 9 and 12 assign the references of Invoice objects to payableObjects(0) and payableObjects(2), respectively. Lines 1011 and 1314 assign the references of SalariedEmployee objects to payableObjects(1) and payableObjects(3), respectively. These assignments are allowed because an Invoice is an IPayable, and a SalariedEmployee is an Employee and an Employee is an IPayable. Lines 2025 use a For Each...Next statement to polymorphically process each IPayable object in payableObjects, displaying the object as a String, along with the payment amount due. Line 22 invokes method ToString via an IPayable variable to get the String representation of each object. Line 24 invokes IPayable method GetPaymentAmount to obtain the payment amount for each object in payableObjects, regardless of the actual type of the object. The output reveals that the method calls in lines 2224 invoke the appropriate class's implementation of methods ToString and GetPaymentAmount. For instance, when currentEmployee refers to an Invoice during the first iteration of the For Each loop, class Invoice's ToString and GetPaymentAmount methods execute. Software Engineering Observation 11.5
11.7.7. Common Interfaces of the .NET Framework Class Library (FCL)In this section, we overview several common interfaces found in the .NET class library. These interfaces are implemented and used in the same manner as the interfaces you create. Figure 11.16 overviews a few of the more popular interfaces of the .NET class library that we use in this book.
|