12.7 Case Study: Creating and Using Interfaces

Our next example (Figs. 12.11–12.14) 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 several invoices (i.e., bills for goods purchased). Though applied to unrelated things (i.e., employees 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 can require unrelated classes to implement a set of common methods (e.g., a method that calculates a payment amount)? Interfaces offer exactly this capability.

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 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 must permit users to perform but does not specify how they’re performed. Similarly, the interface between 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 particular manual-transmission car to drive just about any other. The components of each car may look a bit different, but the general purpose 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 named IPayable that 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. An interface declaration begins with the keyword interface and can contain only

  • abstract methods,

  • abstract 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 interface members are implicitly declared both public and abstract. In addition, each interface can extend one or more other interfaces to create a more elaborate interface that other classes can implement.

Common Programming Error 12.5

It’s a compilation error to explicitly declare an interface member public or abstract, because they’re redundant in interface-member declarations. It’s also a compilation error to specify in an interface any implementation details, such as concrete method declarations.

Implementing an Interface

A class must specify that it implements the interface by listing the interface name after the colon (:) in the class declaration. This is the same syntax 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 does not implement all its members is an abstract class—it must be declared abstract and must contain an abstract declaration for each unimplemented member of the interface. Implementing an interface is like signing a contract with the compiler that states, “I will provide an implementation for all the members specified by the interface, or I will declare them abstract.”

Common Programming Error 12.6

Failing to define or declare any member of an interface in a class that implements the interface results in a compilation error.

Common Methods for Unrelated Classes

An interface is typically used when unrelated classes need to share common methods. This allows objects of unrelated classes to be processed polymorphically—objects of classes that implement the same interface can respond to the same method calls. You can create an interface 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 interface IPayable in each class (e.g., Employee, Invoice) that must be able to calculate a payment amount.

Interfaces vs. Abstract Classes

An interface often is used in place of an abstract class when there’s no default implementation to inherit—that is, no fields and no default method implementations. Like abstract classes, interfaces are typically public types, so they’re normally declared in files by themselves with the same name as the interface and the .cs filename extension.

12.7.1 Developing an IPayable Hierarchy

To build an app that can determine payments for employees and invoices alike, we first create an interface named IPayable. Interface IPayable contains method GetPayment-Amount that returns a decimal amount to be paid for an object of any class that implements the interface. Method GetPaymentAmount is a general-purpose version of method Earnings of the Employee hierarchy—method Earnings calculates a payment amount specifically for an Employee, while GetPaymentAmount can be applied to a broad range of unrelated objects. After declaring interface IPayable, we introduce class Invoice, which implements interface IPayable. We then modify class Employee such that it also implements interface IPayable.

Classes Invoice and Employee both represent things for which the company must be able to calculate a payment amount. Both classes implement IPayable, so an app can invoke method GetPaymentAmount on Invoice objects and Employee objects alike. This enables the polymorphic processing of Invoices and Employees required for our company’s accounts-payable app.

Good Programming Practice 12.1

By convention, the name of an interface begins with I (e.g., IPayable). 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 purpose in a general manner, because the method may be implemented by a broad range of unrelated classes.

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 interface IPayable. The UML distinguishes an interface from a class by placing the word “interface” in guillemets (≪ and ≫) above the interface name. The UML expresses the relationship between a class and an interface through 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 class to the interface. The diagram in Fig. 12.10 indicates that classes Invoice and Employee each realize (i.e., implement) interface IPayable. As in the class diagram of Fig. 12.2, class Employee appears in italics, indicating that it’s an abstract class. Concrete class SalariedEmployee extends Employee and inherits its base class’s realization relationship with interface IPayable. Figure 12.10 could include Section 12.5’s entire Employee class hierarchy—to keep the forthcoming example small, we did not include classes HourlyEmployee, CommissionEmployee and BasePlusCommissionEmployee.

Fig. 12.10 IPayable interface and class hierarchy UML class diagram.

12.7.2 Declaring Interface IPayable

The declaration of interface IPayable begins in Fig. 12.11 at line 3. Interface IPayable contains public abstract method GetPaymentAmount (line 5). The method cannot be explicitly declared public or abstract. Interfaces can have any number of members and interface methods can have parameters.

Fig. 12.11 IPayable interface declaration.

Alternate View

1    // Fig. 12.11: IPayable.cs
2    // IPayable interface declaration.
3    public interface IPayable
4    {
5       decimal GetPaymentAmount(); // calculate payment; no implementation
6    }

12.7.3 Creating Class Invoice

We now create class Invoice (Fig. 12.12) to represent a simple invoice that contains billing information for one kind of part. The class contains properties PartNumber (line 7), PartDescription (line 8), Quantity (lines 23–39) and PricePerItem (lines 42–58) that indicate the part number, the description of the part, the quantity of the part ordered and the price per item. Class Invoice also contains a constructor (lines 13–20) and a ToString method (lines 61–63) that returns a string representation of an Invoice object. The set accessors of properties Quantity and PricePerItem ensure that quantity and pricePer-Item are assigned only nonnegative values.

Fig. 12.12 Invoice class implements IPayable.

Alternate View

 1    // Fig. 12.12: Invoice.cs
 2    // Invoice class implements IPayable.
 3    using System;
 4
 5    public class Invoice : IPayable
 6    {
 7       public string PartNumber { get; }
 8       public string PartDescription { get; }
 9       private int quantity;
10       private decimal pricePerItem;
11
12       // four-parameter constructor
13       public Invoice(string partNumber, string partDescription, int quantity,
14          decimal pricePerItem)
15       {
16          PartNumber = partNumber;
17          PartDescription = partDescription;
18          Quantity = quantity; // validate quantity
19          PricePerItem = pricePerItem; // validate price per item
20       }
21
22       // property that gets and sets the quantity on the invoice
23       public int Quantity
24       {
25          get
26          {
27             return quantity;
28          }
29          set
30          {
31             if (value < 0) // validation
32             {
33                throw new ArgumentOutOfRangeException(nameof(value),
34                   value, $"{nameof(Quantity)} must be <= 0");
35             }
36
37             quantity = value;
38          }
39       }
40
41       // property that gets and sets the price per item
42       public decimal PricePerItem
43       {
44          get
45          {
46             return pricePerItem;
47          }
48          set
49          {
50             if (value < 0) // validation
51             {
52                throw new ArgumentOutOfRangeException(nameof(value),
53                   value, $"{nameof(PricePerItem)} must be >= 0");
54              }
55
56              pricePerItem = value;
57           }
58       }
59
60       // return string representation of Invoice object
61       public override string ToString() =>
62          $"invoice:
part number: {PartNumber} ({PartDescription})
" +
63          $"quantity: {Quantity}
price per item: {PricePerItem:C}";
64
65       // method required to carry out contract with interface IPayable
66       public decimal GetPaymentAmount() => Quantity * PricePerItem;
67    }

Line 5 indicates that class Invoice implements interface IPayable. Like all classes, class Invoice also implicitly inherits from class object. All objects of a class can implement multiple interfaces, in which case they have the is-a relationship 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


public class ClassName: BaseClassName, FirstInterface, SecondInterface, …

When a class inherits from a base class and implements one or more interfaces, the class declaration must list the base-class name before any interface names.

Software Engineering Observation 12.6

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 implement any number of interfaces.

Class Invoice implements the one method in interface IPayable—method GetPaymentAmount is declared in line 66. The method calculates the amount required to pay the invoice. The method multiplies the values of quantity and pricePerItem (obtained through the appropriate properties) and returns the result. This method satisfies the implementation requirement for the method in interface IPayable—we’ve fulfilled the interface contract with the compiler.

12.7.4 Modifying Class Employee to Implement Interface IPayable

We now modify class Employee to implement interface IPayable (Fig. 12.13). This class declaration is identical to that of Fig. 12.4 with two exceptions:

  • Line 3 of Fig. 12.13 indicates that class Employee now implements interface IPayable.

  • Line 27 implements interface IPayable’s GetPaymentAmount method.

Notice that GetPaymentAmount simply calls Employee’s abstract method Earnings. At execution time, when GetPaymentAmount is called on an object of an Employee derived class, GetPaymentAmount calls that class’s concrete Earnings method, which knows how to calculate earnings for objects of that derived-class type.

Fig. 12.13 Employee abstract base class that implements interface IPayable.

Alternate View

 1  // Fig. 12.13: Employee.cs
 2  // Employee abstract base class that implements interface IPayable.
 3  public abstract class Employee : IPayable
 4  {
 5     public string FirstName { get; }
 6     public string LastName { get; }
 7     public string SocialSecurityNumber { get; }
 8
 9     // three-parameter constructor
10     public Employee(string firstName, string lastName,
11        string socialSecurityNumber)
12     {
13        FirstName = firstName;
14        LastName = lastName;
15        SocialSecurityNumber = socialSecurityNumber;
16     }
17
18     // return string representation of Employee object, using properties
19     public override string ToString() => $"{FirstName} {LastName}
" +
20        $"social security number: {SocialSecurityNumber}";
21
22     // abstract method overridden by derived classes
23     public abstract decimal Earnings(); // no implementation here
24
25     // implementing GetPaymentAmount here enables the entire Employee
26     // class hierarchy to be used in an app that processes IPayables
27     public decimal GetPaymentAmount() => Earnings();
28  }

Derived Classes of Employee and Interface IPayable

When a class implements an interface, the same is-a relationship as inheritance applies. Class Employee implements IPayable, so we can say that an Employee is an IPayable, and thus any object of an Employee derived class also is an IPayable. So, if we update the class hierarchy in Section 12.5 with the new Employee class in Fig. 12.13, then SalariedEmployees, HourlyEmployees, CommissionEmployees and BasePlusCommissionEmployees are all IPayable objects. Just as we can assign the reference of a SalariedEmployee derived-class object to a base-class Employee variable, we can assign the reference of a SalariedEmployee object (or any other Employee derived-class object) to an 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 12.7

Inheritance and interfaces are similar in their implementation of the is-a relationship. 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.8

The is-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.

12.7.5 Using Interface IPayable to Process Invoices and Employees Polymorphically

PayableInterfaceTest (Fig. 12.14) illustrates that interface IPayable can be used to process a set of Invoices and Employees polymorphically in a single app. Lines 12–16 create a List<IPayable> named payableObjects and initialize it with four new objects—two Invoice objects (lines 13–14) and two SalariedEmployee objects (lines 15–16). These assignments are allowed because an Invoice is an IPayable, a SalariedEmployee is an Employee and an Employee is an IPayable. Lines 22–28 use a foreach statement to process each IPayable object in payableObjects polymorphically, displaying the object as a string, along with the payment due. Line 25 implicitly invokes method ToString using the IPayable interface reference payable, even though ToString is not declared in interface IPayable—all references (including those of interface types) refer to objects of classes that extend object and therefore have a ToString method. Line 27 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 25 and 27 invoke the appropriate class’s implementation of methods ToString and GetPaymentAmount.

Software Engineering Observation 12.9

All methods of class object can be called by using a reference of an interface type—the reference refers to an object, and all objects inherit the methods of class object.

 

Fig. 12.14 Tests interface IPayable with disparate classes.

Alternate View

 1    // Fig. 12.14: PayableInterfaceTest.cs
 2    // Tests interface IPayable with disparate classes.
 3    using System;
 4    using System.Collections.Generic;
 5
 6    class PayableInterfaceTest
 7    {
 8       static void Main()
 9       {
10          // create a List<IPayable> and initialize it with four
11          // objects of classes that implement interface IPayable
12          var payableObjects = new List<IPayable>() {                         
13             new Invoice("01234", "seat", 2, 375.00M),                        
14             new Invoice("56789", "tire", 4, 79.95M),                         
15             new SalariedEmployee("John", "Smith", "111-11-1111", 800.00M),   
16             new SalariedEmployee("Lisa", "Barnes", "888-88-8888", 1200.00M)};
17
18          Console.WriteLine(
19             "Invoices and Employees processed polymorphically:
");
20
21          // generically process each element in payableObjects
22          foreach (var payable in payableObjects)
23          {
24             // output payable and its appropriate payment amount
25             Console.WriteLine($"{payable}");
26             Console.WriteLine(
27                $"payment due: {payable.GetPaymentAmount():C}
");
28          }
29       }
30    }

Invoices and Employees processed polymorphically:

invoice:
part number: 01234 (seat)
quantity: 2
price per item: $375.00
payment due: $750.00

invoice:
part number: 56789 (tire)
quantity: 4
price per item: $79.95
payment due: $319.80

salaried employee: John Smith
social security number: 111-11-1111
weekly salary: $800.00
payment due: $800.00

salaried employee: Lisa Barnes
social security number: 888-88-8888
weekly salary: $1,200.00
payment due: $1,200.00

12.7.6 Common Interfaces of the .NET Framework Class Library

In this section, we overview several common interfaces defined in the .NET Framework Class Library. These interfaces are implemented and used in the same manner as those you create (e.g., interface IPayable in Section 12.7.2). Implementing these interfaces enables you to incorporate objects of your own types into many important aspects of the Framework Class Library. Figure 12.15 overviews several commonly used Framework Class Library interfaces and why you might implement them in your own types.

Fig. 12.15 Common interfaces of the .NET Framework Class Library.

Interface Description
IComparable C# contains several comparison operators (e.g., <, <=, >, >=, ==, !=) that allow you to compare simple-type values. Section 10.13 showed that you can overload these operators for your own types. Interface IComparable can be used to allow objects of a class that implements the interface to be compared to one another. The interface contains one method, CompareTo, which compares the object that calls the method to the object passed as an argument. Classes must implement CompareTo to return a value indicating whether the object on which it’s invoked is less than (negative integer return value), equal to (0 return value) or greater than (positive integer return value) the object passed as an argument, using any criteria you specify. For example, if class Employee implements IComparable, its CompareTo method could compare Employee objects by their earnings amounts. Interface IComparable is commonly used for ordering objects in a collection such as an array. We use IComparable in Chapter 20, Generics, and Chapter 21, Generic Collections; Functional Programming with LINQ/PLINQ.
IComponent Implemented by any class that represents a component, including Graphical User Interface (GUI) controls (such as buttons or labels). Interface IComponent defines the behaviors that components must implement. We discuss IComponent and many GUI controls that implement this interface in Chapter 14, Graphical User Interfaces with Windows Forms: Part 1, and Chapter 15, Graphical User Interfaces with Windows Forms: Part 2.
IDisposable Implemented by classes that must provide an explicit mechanism for releasing resources. Some resources can be used by only one program at a time. In addition, some resources, such as files on disk, are unmanaged resources that, unlike memory, cannot be released by the garbage collector. Classes that implement interface IDisposable provide a Dispose method that can be called to explicitly release resources that are explicitly associated with an object. We discuss IDisposable briefly in Chapter 13, Exception Handling: A Deeper Look. You can learn more about this interface at http://msdn.microsoft.com/library/ system.idisposable. The MSDN article Implementing a Dispose Method at http://msdn.microsoft.com/library/fs2xkftw discusses the proper implementation of this interface in your classes.
IEnumerator Used for iterating through the elements of a collection (such as an array or a List) one element at a time—the foreach statement uses an IEnumerator object to iterate through elements. Interface IEnumerator contains method MoveNext to move to the next element in a collection, method Reset to move to the position before the first element and property Current to return the object at the current location. We use IEnumerator in Chapter 21. All IEnumberable objects (Chapter 9) provide a GetEnumerator method that returns an IEnumerator object.
..................Content has been hidden....................

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