Interfaces and Loose Coupling in Java by Charlie Calvert

By: Charlie Calvert

Abstract: In the first part of this two part article you will learn how to use interfaces to define a contract between two classes that works at a high level of abstraction and promotes reuse.

Copyright 2003 © by Charlie Calvert

The extensive use of interfaces is one of the most powerful features of the Java Development Kit. Java takes full advantage of the power of interfaces and uses them to provide standards that help us build easily reusable code.

In this two part article I am going to look first at interfaces in general, and then at one narrow case in which Java uses interfaces and events to help you provide a means for loosely coupling objects. This latter technology promotes a simple to use, "plug and play," type of object reuse.

What is Loose Coupling

The term "loose coupling" probably cannot be defined in a definitive manner. It is popularly used to describe the way web services allow clients and servers to be created in entirely separate development processes. However, I am not going to use the word in that context.

Instead, I am going to show how interfaces can provide a high degree of autonomy for individual objects used inside a single application. The objects I will explore will have very few dependencies on other objects. In this sense, they are "loosely" coupled to the other objects in their program. Because the objects are so autonomous, they will be easy to maintain and easy to reuse.

As this article will show, combining interfaces with events can provide a simple, easy means to allow developers to promote reuse. The ultimate goal is to allow the creation of objects that can be used by multiple client objects in much the same way that a web service can be used by multiple clients. These loosely coupled objects will have few direct dependencies binding them together. Furthermore, the dependencies that they do have should be defined by clear standards that can be easily replicated by other clients that wish to consume these objects. The establishment by an object of a clear standard, of a well defined contract, makes that object easily reusable.

Thinking about Interfaces

Someone on the Java development team who understood interfaces decided to make them a big part of the Java SDK. There are hundreds of examples in the J2SE SDK of the correct way to use interfaces. This article is going to focus on only a few of them.

Here are two key benefits derived from using interfaces:

  • An interface provides a means of setting a standard. It defines a contract that promotes reuse. If an object implements an interface then that object is promising to conform to a standard. An object that uses another object is called a consumer. An interface is a contract between an object and its consumer.

  • An interface also provides a level of abstraction that makes programs easier to understand. Interfaces allow developers to start talking about the general way that code behaves without having to get in to a lot of detailed specifics.

The next few section of the text will tackle each of these benefits in turn.

Why an Interface Defines a Contract

Contracts are important because they promote reuse. In the west, most of us have a contract that when we meet one another in formal situations we will shake hands as a way of greeting. Having this contract simplifies the act of meeting someone. In the same way, we have a contract that states that saying goodbye in a telephone conversation means that the conversation is over. If that convention, if that standard, if that contract, did not exist then phone conversations would be more difficult. The exact same purpose is served by the contract established by an interface: It provides a standard way of handling a particular task. A interface provides a good way of establishing the convention that two objects should live by when they form a connection.

How an Interface Defines a Contract

You can declare methods in an interface, but you cannot use the interface to implement those methods. Instead, you use a class to implement the methods found in one or more interfaces.

Consider the following simple interface:

public interface Runnable 
{
    public abstract void run();
}

This class provides a declaration for a method called run. It does not, and cannot, provide an implementation for that method. This particular interface states that any class that implements Runnable will contain a method called run that is declared to be public and void. Here is a class that implements the Runnable interface:

public class MyClass implements Runnable
{
  public void run()
  {
    System.out.println("I implement the runnable interface");
  }
}

In saying that MyClass implements the Runnable interface, we are saying that it is guaranteed to conform to a particular standard. That standard states that any class which implements Runnable must contain a method called run which is declared to be both public and void.

It should now be clear to you how you can use an interface to define a contract between an object and its consumer. In particular, the interface promises that a particular class will contain certain methods. The interface is a contract between that class and the class that uses it. The contract states that the implementing class contains certain methods with certain signatures. It is the basis on which a relationship can be established between an object and its consumer.

Capturing Abstractions: Interfaces and UML

By now you might be getting the sense that interfaces aren't really as complicated as they may have seemed at first. In fact, there are few concepts in programming that are much simpler than interfaces. There is no mystery here at all – at least not on the syntactical level. As the Runnable – MyClass example shows, the syntax for using an interface in Java is very simple.

Interfaces represent a fairly high level of abstraction. If we talk about a class that implements Runnable, then we are not talking about a specific class. We are talking about a group of classes that implements a particular behavior.

This abstraction can be captured in a UML diagram. In particular, Figure 1 shows the relationship between the Runnable interface and MyClass.

Figure 1: The dotted line ending in a closed triangle is the UML way of saying that MyClass implements that Runnable interface.

Any class that implements an interface can be captured in diagram similar to the one shown in Figure 1. All one needs to do is draw a dotted line ending in a closed triangle between the implementing class and the interface that it implements.

Why is this useful? Why do we care? What is it about this diagram that adds any value to our program?

A UML diagram is easy to understand. In complex programs, with dozens or even hundreds of classes and interfaces, it can be very hard to see the relationship between the classes you have created. As they say, a picture is worth a thousand words. It is easier to look at a picture and see the relationship between objects than it is to study many source files and try to see the relationship between the chunks of code found in each file.

Interfaces are all about making your life as a programmer easier. Just as the convention of shaking hands makes it easier to meet someone new, so does the existence of an interface make it easier to allow two objects to begin speaking to one another. UML diagrams make it easier for you to see the relationship between classes.

Defining Abstract Behavior with Interfaces

UML diagrams capture one way in which interfaces can help programmers deal with complex behavior through relatively easy to understand abstractions. There is, however, another sense in which abstractions can be captured by interfaces.

In the J2SE SDK, there are a set of classes all of which implement the List interface. Examples of these classes include Vector, ArrayList and LinkedList.

Consider the following simple class:

import java.util.Vector;
import java.util.ArrayList;
import java.util.LinkedList;

public class Untitled1
{
  ArrayList arrayList = new ArrayList();
  Vector vector = new Vector();
  LinkedList linkedList = new LinkedList();
}

The Untitled1 class creates instances of three classes which implement the List interface. Figure 2 shows what class Untitled1 looks like in a UML diagram. Figure 3, 4 and 5 show that ArrayList, Vector and LinkedList all implement the List interface. Figure 6 shows a UML view of the List interface.

Figure 2: In this UML dialog a solid line ending in an open arrow shows that Untitled1 contains instances of the ArrayList, LinkedList and Vector classes. Compare with the dotted line and closed arrow symbol shown in Figure 3.

Figure 3. ArrayList implements the List interface.

Figure 4. LinkedList implements the List interface.

Figure 5. Vector implements the List interface.

Figure 6. The List interface as seen in a UML diagram. The plus signs represent public methods. Compare with Figure 2, where JBuilder standard icons from the Structure Pane are displayed. JBuilder allows displaying UML in either mode.

The ArrayList, LinkedList and Vector classes all conform to the contract established by the List interface. Unlike the Runnable interface, the List interface declares multiple methods. By conforming to this contract, the ArrayList, LinkedList and Vector classes all promise to implement the methods of the List interface such as add(), get(), indexOf() and isEmpty(). In other words, these classes all display the behavior associated with the List interface. Is this sense, they all belong to the same family.

Most experienced drivers can pilot any reasonably sized car. They can do this because the interface for a car is the same in most vehicles, whether that car is a Honda, a Ford or a BMW. In the same way, most developers who know the List interface can use the ArrayList, LinkedList and Vector classes. Just as all cars have a steering wheel and a transmission, so do all classes that implement the List interface have methods such as add(), get() and indexOf(). In this sense, all these classes behave in the same manner.

To illustrate this point in a practical example, let's extend MyClass to support the List interface. An example of how to do this is shown in Listing 1.

Listing 1: A class that uses the List interface.

package untitled10;

import java.util.List;
import java.util.Iterator;


public class MyClass implements Runnable
{
  List myList = null;

  public MyClass(List myList)
  {
    this.myList = myList;
  }

  private String isListEmpty(List myList)
  {
    if (myList.isEmpty())
      return "True";
    else
      return "False";
  }

  private void show(List myList)
  {
    Iterator itr = myList.iterator();
    while (itr.hasNext())
    {
      System.out.println((String)itr.next());
    }
  }

  public void run()
  {
    isListEmpty(myList);
    myList.add("Sam");
    myList.add("Mary");
    myList.add("Tom");
    myList.add("Sue");


    Object item = myList.get(1);
    System.out.println("Retrieved Item: " + item);
    System.out.println("Index of retrieved item: " + myList.indexOf(item));
    System.out.println("The list before removing an item called " + item + ":");
    show(myList);
    myList.remove(item);
    System.out.println("The list after removing an item called "  + item);
    show(myList);
  }
}

Notice that the constructor for MyClass now takes an object that supports the List interface:

  List myList = null;

  public MyClass(List myList)
  {
    this.myList = myList;
  }

Notice furthermore that the other code in MyClass exercises the List interface in various ways. The output from this class might look like this:

Retrieved Item: Mary
Index of retrieved item: 1
The list before removing an item called Mary:
Sam
Mary
Tom
Sue
The list after removing an item called Mary
Sam
Tom
Sue

Any instance of the Vector, ArrayList, or LinkedList class can now be passed into MyClass. For instance, the following code is perfectly legal.

    ArrayList list = new ArrayList();
    Thread thread = new Thread(new MyClass(list));
    thread.start();

So is the code shown in this example:

    Vector list = new Vector();
    Thread thread = new Thread(new MyClass(list));
    thread.start();

And so is this code:

    LinkedList list = new LinkedList();
    Thread thread = new Thread(new MyClass(list));
    thread.start();

As you can see, one class, called MyClass, is able to consume three entirely different classes called Vector, ArrayList and LinkedList. This is a valuable form of reuse. It is made possible by the fact that the Vector, ArrayList and LinkedList classes all support the List interface.

Suppose you create a standard JBuilder application that supports a class called Frame1 which is a descendant of JFrame. Suppose that Frame1 contains a method for handling button clicks that looks like this:

class Frame1 extends JFrame
{

... Code omitted here

  void jButton1_actionPerformed(ActionEvent e)
  {
    LinkedList list = new LinkedList();
    Thread thread = new Thread(new MyClass(list));
    thread.start();
  }
}

Notice that the relationship between Frame1, LinkedList and MyClass is very abstract. All Frame1 needs to know about MyClass is that it supports the Runnable interface, and can thus be placed in a thread. And all that MyClass needs to know about Frame1 is that it knows how to create a thread. MyClass does not even need to know what type of class it is being passed in its constructor: new MyClass(list). All MyClass needs to know is that the class it is being passed supports the List interface.

All these classes are linked together with a very high degree of abstraction. The knowledge they have about one another is on a strictly need to know basis. They don't know any more about each other than is absolutely necessary for them to interact. In short, they are relatively loosely coupled.

Summary

In this article you have learned how interfaces can be used to specify a contract between two classes. This interface provides a standard means for one class to consume another class.

You also learned that an interface provides a high level of abstraction that allows you to easily define certain well know patterns of behavior. Examples of these patterns were illustrated by the Runnable and List interfaces.

This article also illustrated that objects which conform to a particular interface can support a form of "loose coupling." This highly abstracted relationship between two classes supports an admirable degree of reuse.

This is the end of the first part of this article. In the second part you will learn how to use a particular interface called ActionListener to define a way for one class to consume another class. This relationship will be so loosely coupled that almost any class can quickly learn to consume any object that conforms to the contract defined by this interface.


Server Response from: ETNASC04