Concurrent Programming in Java
© 1996 Doug Lea

Implementing Basic Design Patterns in Java


Contents

Interfaces

An interface encapsulates a coherent set of services and attributes (broadly, a Role), without explicitly binding this functionality to that of any particular object or code.

Interfaces are ``more abstract'' than classes since they don't say anything at all about representation or code. All they do is describe public operations. For example, the java.lang.Runnable interface is just:

public interface Runnable {
 public abstract void run();
}
Notes:
  1. The keyword abstract means that no code is supplied for this method. It is strictly optional to bother listing it in interfaces. All methods defined in interfaces must be abstract.
  2. All methods defined in interfaces must be public.
  3. The only other kind of declaration allowed in an interface is to list public static final constants.

Interfaces turn out to be a more useful concept than they might first appear. They are vital tools for stepping up from writing occasional one-shot classes to writing extensible packages and frameworks of classes that form the basis for suites of applications:

Interfaces are otherwise used pretty much just like classes in Java. For example, you can write a method that accepts any object obeying a stated interface and invoke methods on it, as in:

class Foo {
  void doIt(Runnable r) { r.run(); }
}

Simple one-method interfaces are often needed to standardize naming conventions. The Runnable interface, and its run() method, used for argumentless, resultless actions is so common to be predefined in Java. While not predefined, other canonical forms also have commonly adopted names. These include:

public interface Procedure { // apply some operation to a single object
  public void    apply(Object x);
}

public interface Generator { // return an object
  public Object  next();
}

public interface Function  { // apply an operation; return an object
  public Object  map(Object x);
}

public interface Predicate { // test some property of an object
  public boolean test(Object x);
}

public interface Combiner  { // return result of a binary operation
  public Object  combine(Object x, Object y);
}

Subinterfaces

One interface may be described as a subinterface of another if it extends its properties (i.e., contains additional methods.) In some languages one interface need not explicitly list that it is a subinterface of another -- if it contains all of the same features, plus possibly more, it is considered a subinterface. (This is known as type conformance.)

But in Java, each subinterface must be explicitly listed as extending one or more other interfaces, implicitly inheriting all of the super-interface operations. For a classic example:

interface File {
  public void open(String name);
  public void close();
}

interface ReadableFile extends File {
  public byte readByte();
}

interface WritableFile extends File {
  public void writeByte(byte b);
}

interface ReadWriteFile extends ReadableFile, WritableFile {
  public void seek(int position);
}

It is common when defining interface hierarchies to use fairly fine-grained interfaces at the top, each defining only a few operations, and to use (multiple) interface inheritance to define ``fatter'', more useful ones as various combinations of these basic bits of functionality.

Implementation Classes

A class can implement an interface merely by:
  1. Declaring that it does so.
  2. Actually implementing the methods defined in the interface.

For example, to implement Runnable in the dumbest (but fastest!) possible way:

class FastRunner implements Runnable { public void run() {} }

Note that there's a difference between a method defined not to do anything (i.e., with a null body) versus an abstract method, which is not defined at all.

Subclasses can also implement unrelated interfaces. For example, to create a subclass of some pre-exisiting class that also implements Runnable to execute a particular method when run() is invoked:

class URLReader {
  protected String url_;
  protected byte[] buffer_;
  public URLReader(String url, byte[] buffer) {
    url_ = url; buffer_ = buffer;
  }
  public void read() { /* read into buffer */ }
}

class RunnableURLReader extends URLReader implements Runnable {
  public RunnableURLReader(String url, byte[] buffer) {
    super(url, buffer);
  }
  public void run() { read(); }
}

Abstract Classes

Abstract classes form an interesting and useful middle ground between interfaces and classes. Abstract classes are different than ordinary classes in that they declare one or more interface-style abstract methods in addition to normal methods and instance variables. Such classes must be explicitly declared as abstract. For example:
abstract class MachinePart {
  protected String partID_;
  public String partID() { return partID_; }

  protected MachinePart(String id) { partID_ = id; }

  public abstract boolean canConnectTo(MachinePart other);
}

Here, the MachinePart class set down a common representation for partIDs that holds for all subclasses. But the declaration of canConnectTo is abstract since it has to be implemented differently in every public subclass. (Alternatively, we could have provided a default implementation, say to return false.)

Every MachinePart subclass gets its partID mechanics for free, but must specially implement canConnectTo. And as usual, it can further extend the class or even implement additional interfaces:

class Gadget extends MachinePart {
  public Gadget(String id) { super(id); }

  public boolean canConnectTo(MachinePart other) {
    if (other instanceof Gadget) return true;
    else if (other.partID().startsWith("Universal")) return true;
    else return false;
  }

  public void specialGadgetMethod() { ... }
}

Abstract classes are like interfaces in one other sense: Neither are directly instantiable via new. So you cannot write code like:

  Runnable r = new Runnable(); // wrong
  MachinePart p = new MachinePart(); // wrong

The reason they are not instantiable is that there is no code supporting one or more methods, so no such object can be constructed. This is true even though abstract classes often have constructors. But these are called only from subclass constructors.

There are several further variations on this interplay between interfaces and classes. For example, it is legal in Java to say that an abstract class implements an interface even though it doesn't actually define one or more of the methods, but instead leaves them for its own subclasses to define. Such odd-sounding techniques tend to become standard tricks of the trade when designing large frameworks and packages.

Factories

The fact that interfaces are not instantiable turns out to be a useful property. It forces you to separate the notions of calling methods from constructing objects. A client of an interface doesn't really care about the particular object or its immediate implementation class that performs a service. So it has no business invoking a constructor declared within a particular implementation class. Instead, when designing sets of components via interfaces, you can create factories, as described in several related patterns in the Design Patterns book.

A factory class exists just to help create instances of other classes. Factories often have methods that produce instances of several related classes, but all in a compatible way. Factories should themselves be defined via interfaces, so the client need not know which particular factory object it is using. Ideally, all such matters can be reduced to a single call to construct the appropriate ``master'' factory in a client application.

Among the most well-known examples of factories are UI toolkits that are designed to run on different windowing systems. There might be interfaces for things like:

interface ScrollBar { ... }
interface MenuBar   { ... }
...

And associated classes implementing them on different windowing systems:

class MotifScrollBar implements ScrollBar { ... }
class Win95ScrollBar implements ScrollBar { ... }
...

And a factory interface that also doesn't commit to representation:

interface Factory {
  public abstract ScrollBar newScrollBar();
  public abstract MenuBar   newMenuBar();
  ...
}

But implementation classes that do:

class MotifFactory implements Factory {
  public ScrollBar newScrollBar() { return new MotifScrollBar(...); }
  ...
}

Finally, to get an application up and running on different systems, only one bit of implementation-dependent code is needed:

class App {
  public static void main(String args[]) {

   Factory wf = null;
   if (args[0].equals("Motif"))
     wf = new MotifFactory();
   else ...

   startApp(wf);
 }
}

All other objects construct widgets using the factory passed around (or, more likely, kept in a central data structure), never knowing or caring what windowing system they happen to be running on.

Note: The Java AWT uses a strategy that is similar in concept but different in detail for achieving implementation independence, via a set of ``peer'' implementation classes that are specialized for the platform that the Java session is being run on, and instantiated when user-level AWT objects are constructed.


Doug Lea Last modified: Mon Mar 2 07:15:47 EST 1998