5.2. Interface Example

In this section, we will provide you with a full example showing how to create:

  1. An Interface

  2. Implementing Classes

  3. A Driver Program (Where the real benefit comes in!)

As we go through this example, we will explain the syntax, why you would want to use an interface, and where it is appropriate to do so in your applications.

5.2.1. Creating an Interface

The role of the interface is to define the set of behaviors (methods) that all of the classes must contain. In the example of the payment processing services, the interface would define each behavior (method) that each service must have. For example, we might say that all three services mentioned earlier (Visa, PayPal, and Affirm) must be able to process payments and print receipts. Those are the two critical actions that all payment processors must be able to perform. We might also say that each service charges a 1.5% transaction fee to process a payment.

In this case, we could define the interface in Java as follows:

An example interface with two abstract methods and a constant.
public interface PaymentProcessor {

    /** The fee for processing a transaction. */
    public static final double FEE_PERCENTAGE = 1.5;

    /**
     * Processes a payment for the specified {@code amount}. The details
     * of how the payment is processed depends on the implementing class.
     *
     * @param amount the amount to process.
     * @return if payment is successful
     */
    public boolean void processPayment(double amount);

    /**
     * Prints a receipt to the specified {@code customer} for the
     * specified {@code amount}.
     *
     * @param customer the name of the customer who made the payment.
     * @param amount the amount of the payment.
     */
    public abstract void printReceipt(String customer, double amount);
} // PaymentProcessor

In order to understand the code above, we first need to remember that the goal of the interface is to define the methods that each class must contain. More specifically, the interface is the contract that each implementing class must follow. In our example, we will have three implementing classes (Visa, PayPal, and Affirm). Once we set up the relationship, the Java compiler will be able to enforce the contract ensuring that all three implementing classes properly implement the interface and allowing our program to work seamlessly with all three!

More Formally: A Java interface is a reference type composed of abstract methods and constants. An abstract method is a non-static (instance) method without an implementation (body). Think of creating a class, adding the method signatures, but not putting any code in the methods. Constants are variables (static or not) that are declared using the final keyword.

Note

As of Java 8, the technical definition for an interface allows it to contain only the following: abstract methods, constants, private methods, static methods, nested types, and default implementation methods ([INTERFACE]). In 1302, our interfaces will mostly contain only abstract methods and constants. Nested types and default methods will not be covered in this course.

5.2.2. Creating Implementing Classes

In order to leverage the power of interfaces, you must combine the interface with regular classes that implements the abstract methods in the interface (called the implementing classes) as unimplemented methods are not particularly useful by themselves. We will use Visa and PayPal as our two implementing classes although you could certainly create others that represent other payment processing services.

Remember, the interface is a contract that all implementing classes must follow (specifies the set of things the implementing classes can do). Multiple classes can implement the same interface, each providing its own implementation of the contracted functionality. For this reason, the documentation for an interface must describe what a method does and not necessarily how it should do it. Such documentation is usually written using Javadoc comments in the interface as seen in the interface above.

In our example, both implementing classes (Visa and PayPal) must have methods called processPayment and printReceipt since those are the two abstract methods in the interface. Here is how those classes might be implemented:

Sample implementation for implementing class, Visa
public class Visa implements PaymentProcessor {
   public boolean processPayment(double amount) {
      // This is a sample implementation. We can safely assume
      // that a real Visa payment requires much more processing. :)

      System.out.println("Processing Visa Card Payment of $" + amount);
      return true;
   } // processPayment

   public void printReceipt(String customer, double amount) {
      System.out.println(customer + " has completed a Visa purchase in the amount of $" + amount);
   } // printReceipt
} // Visa
Sample implementation for implementing class, PayPal
public class PayPal implements PaymentProcessor {
   public boolean processPayment(double amount) {
      // This is a sample implementation. We can safely assume
      // that a real PayPal payment requires much more processing. :)

      System.out.println("Processing PayPal Payment of $" + amount);
      return true;
   } // processPayment

   public void printReceipt(String customer, double amount) {
      System.out.println(customer + " has completed a PayPal purchase in the amount of $" + amount);
   } // printReceipt
} // Visa

A few things that are important to note about the code above:

  1. Both implementing classes include implements PaymentProcessor in their class declaration. This is how the classes agree to uphold the contract laid out in the interface (“sign the contract”). If a class declaration states that a particular interface is implemented by the class, that class will not compile if it does not correctly implement the listed interface. This is how the Java compiler ensures compatibility across impmlementing classes.

  2. Each implementing class implements the methods differently. PayPal and Visa process payments and print receipts in different ways. Our example is overly simplified but you should take a moment to consider how these two payment processing systems would behave differently in a real-world scenario. Even if the real-world scenario is much more complex, the benefits of interfaces is the same.

This may all still seem a bit strange and why we do this in programming has not yet been made clear. Hang in there! Let’s work through an example of a driver program where the real benefits become more clear.

5.2.3. The UGA Bookstore Website (Driver)

Now that we understand how interfaces and implementing classes can be created, it’s important to understand why we might use them in our code and how they can benefit us as programmers!

To demonstrate the benefits of interfaces, we will write a short UGABookstore class with methods allowing customers to purchase items in different ways (with Visa or PayPal). The code below shows how you might write the code to process payments with Visa and PayPal without leveraging the interface. Assume that the UGABookstore class has access to the PaymentProcessor interface and both implementing classes.

Initial UGA Bookstore Class (not using the interface)
public class UGABookstore {

   public boolean purchase(Visa visa, String customerName, double amount) {
       visa.processPayment(amount);
       visa.printReceipt(customerName, amount);
   } // purchase

   public boolean purchase(PayPal paypal, String customerName, double amount) {
       paypal.processPayment(amount);
       paypal.printReceipt(customerName, amount);
   } // purchase

   public static void main(String[] args) {
      // This method is the main entry point of the application and
      // contains calls to the purchase method. Specific code is omitted.

   } // main
} // UGABookstore

Take a moment to look closely at both purchase methods. The only difference between them is the first parameter. This redundancy can be removed by using our previously-created interface.

Test Yourself

  1. How would the code above change if we wanted to support payments from other payment processors without using the PaymentProcessor interface? For example, the other payment processor could be Affirm.

  2. What if there were 20 additional payment processors?

Test Yourself Solution (Open after answering the question above)
  1. We would need to add another purchase method that takes in a reference to an Affirm object.

  2. We would need 20 additional purchase methods. Uh oh…

Now that you’ve had a chance to observe the problem, let’s fix it by using our interface, PaymentProcessor. Here is the updated code:

New and Improved UGA Bookstore Class (using the interface)
public class UGABookstore {

   public boolean purchase(PaymentProcessor payment, String customerName, double amount) {
       payment.processPayment(amount);
       payment.printReceipt(customerName, amount);
   } // purchase

   public static void main(String[] args) {
      // This method is the main entry point of the application and
      // contains calls to the purchase method. Specific code is omitted.

   } // main
} // UGABookstore

Notice that we only need one purchase method now! The purchase method takes in a PaymentProcessor reference which is compatible with any class that implements the interface. We can pass in references to Visa objects, PayPal objects, and any other implementing class that we create in the future.

In other words, this class will not have to change in the future even if we add other payment processing services! Our Bookstore class will be able to seamlessly work with the other methods.

This all may still seem a little strange to you at this point. In the next few sections, we give you an opportunity to work through a hands-on example on Odin. Those sections also dive deeper into some of the details we omitted in this section to help you gain a deeper understanding of the inner workings of interfaces.

As always, do your best and let us know if you have any questions while working through that example.