Lecture 4: Getting (back) into Java, part 2
1 A compact representation for durations
1.1 Interface polymorphism
1.2 Implementing the new representation
2 An abstract base class
2.1 The factory method pattern
2.2 How far should we go?
6.5

Lecture 4: Getting (back) into Java, part 2

1 A compact representation for durations

In the previous lecture we implemented working Duration objects, and we could start writing code with them. (You likely will.) But along the way we may have noticed that working with the hours-minutes-days representation is somewhat awkward. Perhaps when we chose that over a single field representing the total number of seconds, we made a wrong term. Fortunately we can use the compact representation with minimal or no changes to client code by taking advantage of our design choices:

1.1 Interface polymorphism

In the remainder of this lecture we pursue the latter strategy, since all the code required for the former is required for the latter as well. We’ll call the new class CompactDuration; perhaps we should have given DurationImpl a more descriptive name. Once CompactDuration is written, our class hierarchy will look like this:

This is a UML class diagram, and several aspects of it are worth explaning:

This diagram lists all the fields and methods, but often it will make sense to elide those that aren’t relevant to the explanatory goals of the diagram.

So what’s the significance of the structure we’ve designed? Consider the situations of two clients that were written to use durations back when DurationImpl was the only implementation, once CompactDuration implements Duration as well:

UnhappyClient uses the DurationImpl class in its code, perhaps as the type of some fields or method parameters. This means that to use CompactDuration instead, the maintainer of UnhappyClient needs to change its code everywhere that it mentions DurationImpl to use CompactDuration.

HappyClient uses the Duration interface in its code as the type for fields and parameters that need to hold durations. Because a DurationImpl is a Duration, the Durations that we give HappyClient can be DurationImpls and will work properly. And because the new class, CompactDuration is a Duration as well, durations that we use with HappyClient can be CompactDurations as well, with no changes to HappyClient. Because HappyClient refers to the interface Duration rather than a particular class implementing it, we benefit from interface polymorphism—the ability to use multiple classes that implement an interface with a client that depends only on the interface.

In reality, it will often be the case that we have a nearly happy client that limits its mentions of DurationImpl to constructor expressions, since constructing an object requires specifying a class. In this case, we would need to make a limited number of changes to the nearly happy client to update the constructors. We will see later a way to design a client to avoid committing even to calling a particular class’s constructor.

1.2 Implementing the new representation

The new class should be straightforward. We start by writing one of the two constructors (the simpler one) and the single private long field that it initializes:

/**
 * Durations represented compactly, with a range of 0 to
 * 2<sup>63</sup>-1 seconds.
 */
public final class CompactDuration implements Duration {
  /**
   * Constructs a duration in terms of its length in seconds.
   *
   * @param inSeconds the number of seconds (non-negative)
   * @throws IllegalArgumentException {@code inSeconds} is negative
   */
  public CompactDuration(long inSeconds) {
    if (inSeconds < 0) {
      throw new IllegalArgumentException("must be non-negative");
    }

    this.inSeconds = inSeconds;
  }

  private final long inSeconds;
}

When we write the other constructor, we might notice something funny:

/**
 * Constructs a duration in terms of its length in hours, minutes, and
 * seconds.
 *
 * @param hours the number of hours
 * @param minutes the number of minutes
 * @param seconds the number of inSeconds
 * @throws IllegalArgumentException if any argument is negative
 */
public CompactDuration(int hours, int minutes, int seconds) {
  if (hours < 0 || minutes < 0 || seconds < 0) {
    throw new IllegalArgumentException("must be non-negative");
  }

  inSeconds = 3600 * hours + 60 * minutes + seconds;
}

We’ve seen this code before (albeit not all in one place)! Repetition should bother us, but will it bother us enough for us to do something about it? The first two methods probably won’t:

@Override
public long inSeconds() {
  return inSeconds;
}

@Override
public Duration plus(Duration that) {
  return new CompactDuration(this.inSeconds + that.inSeconds());
}

However, when we get to asHms, we see code repeated from both DurationImpl’s unary constructor and its own asHms method:

@Override
public String asHms() {
  int hours = (int) (inSeconds / 3600);  // that overflow again!
  int minutes = (int) (inSeconds / 60 % 60);
  int seconds = (int) (inSeconds % 60);

  return String.format("%d:%02d:%02d", hours, minutes, seconds);
}

Looking forward, we anticipate more repetition in other methods. In a larger, more complex class (which is what we’re practicing for), there’s likely to be even more redundancy. We need a way to factor out the functionality common to the two classes.

2 An abstract base class

When we want to factor out redundancy between functions, we write a helper function and call it from the other functions. When we want to factor out redundancy between classes, we write a helper class and refer to it from the other classes. One way to use the helper class from the classes that share it is to have them extend it. Extension, also known as implementation inheritance, derives a new class from an older one by adding fields, adding methods, and replacing methods. Furthermore, we can design an abstract class with extension in mind, leaving some of its methods missing for subclasses to define.

To get started, we create a new, empty abstract class AbstractDuration and insert it between the Duration interface and its implementations:

We do this by defining the new class so that it implements Duration and modifying the two concrete classes to extend the new AbstractDuration class:

abstract class AbstractDuration implements Duration {
  // nothing yet
}

public final class DurationImpl extends AbstractDuration { ... }

public final class CompactDuration extends AbstractDuration { ... }

Note that AbstractDuration has package scope rather than public, because it’s an implementation detail shared by our two classes but not part of the public API for durations. (There’s no reason we couldn’t make it so.)

Adding AbstractDuration by itself accomplishes nothing, but now we’re set up to start moving common functionality from DurationImpl and CompactDuration into AbstractDuration, allowing them to share. Some of these changes are easy, as four of the methods in DurationImpl will work if moved to AbstractDuration with no changes:

These methods move easily because each of them is written in terms of method inSeconds() from the Duration interface. For AbstractDuration itself, inSeconds() remains undefined, but each concrete subclass must define it, which means that AbstractDuration can rely on each concrete subclass’s inSeconds() method.

2.1 The factory method pattern

Moving plus(Duration) into AbstractDuration is harder. To see why, consider its definition in DurationImpl:

@Override
public Duration plus(Duration that) {
  return new DurationImpl(this.inSeconds() + that.inSeconds());
}

This definition of plus will still work if we move it to AbstractDuration, but with an unfortunate twist: because the DurationImpl constructor is hard-coded into the method, this would cause adding any two classes of Durations to return a DurationImpl. That probably isn’t a good design, as it would be better to allow each class to choose whatever class it likes for the result of plus.

How can we do it better? Instead of hard-coding the constructor call into plus (in AbstractDuration), suppose we had a method that would construct the right class of Duration. Then we could rewrite plus to use that:

@Override
public Duration plus(Duration that) {
  return fromSeconds(this.inSeconds() + that.inSeconds());
}

Now we just need to write fromSeconds so that it constructs durations of the right class, as determined by each concrete subclass of AbstractDuration. A method like fromSeconds, whose purpose is to create (or somehow produce) objects, is called a factory method. When a superclass doesn’t know what classes of objects to create and wants to let its subclasses decide, the factory method pattern provides an answer. AbstractDuration can defer the decision of what class to instantiate to its subclasses by declaring the factory method abstract:1There are two potential points of confusion here: 1) Not every factory method is an instance of the factory method pattern, but rather those that work in the particular way introduced here. 2) We are declaring our factory method to be abstract, but the abstract factory pattern, which we will see later, means something else more specific.

/**
 * Constructs a {@link Duration} in a manner selected by each concrete
 * subclass of this class.
 *
 * @param asSeconds the length in seconds
 * @return the new {@code Duration}
 */
protected abstract Duration fromSeconds(long inSeconds);

We make the method protected because it exists only for communication between AbstractDuration and its subclasses, and does not need to be visible to clients.

Concrete subclasses are required to define any abstract methods of their superclasses, which means that now we just need to define fromSeconds in each of the subclasses of AbstractDuration to do the right thing. That is, in DurationImpl we write

protected Duration fromSeconds(long inSeconds) {
  return new DurationImpl(inSeconds);
}

And in CompactDuration we write

protected Duration fromSeconds(long inSeconds) {
  return new CompactDuration(inSeconds);
}

Now we can move plus to AbstractDuration and it will use its subclasses’ definitions of fromSeconds to construct its result:

Note that # indicates that a method is protected.

2.2 How far should we go?

While at this point we have moved all of the methods to AbstractDuration that we can, that doesn’t mean we’ve eliminated all the redundancy that we can. In particular, let’s return to the method of CompactDuration whose redundancies with parts of DurationImpl motivated creating the abstract class in the first place:

@Override
public String asHms() {
  int hours = (int) (inSeconds / 3600);  // that overflow again!
  int minutes = (int) (inSeconds / 60 % 60);
  int seconds = (int) (inSeconds % 60);

  return String.format("%d:%02d:%02d", hours, minutes, seconds);
}

Compare the above to this constructor and method of DurationImpl:

public DurationImpl(long inSeconds) {
  if (inSeconds < 0) {
    throw new IllegalArgumentException("must be non-negative");
  }

  this.seconds = (int) (inSeconds % 60);
  this.minutes = (int) (inSeconds / 60 % 60);
  this.hours = (int) (inSeconds / 3600);  // overflow :(
}

@Override
public String asHms() {
  return String.format("%d:%02d:%02d", hours, minutes, seconds);
}

CompactDuration.asHms() uses the same code as the DurationImpl(long) constructor to break the duration into hours, minutes, and seconds, and then it uses the same code as DurationImpl.asHms() to do the formatting. Thus, if we are willing to factor out some helper methods, we can make a single point of control for each of these aspects. In particular, we add four protected, static methods to AbstractDuration to define each piece of common logic. For the formatting logic, we define asHms(int, int, int):

protected static String asHms(int hours, int minutes, int seconds) {
  return String.format("%d:%02d:%02d", hours, minutes, seconds);
}

Note that this new asHms method is static, meaning that it doesn’t operate on a particular object, instead taking its input as explicit arguments.

The other three methods are for extracting the three components from the duration. We also take this opportunity to fix the overflow bug from earlier—it’s a good time to do it, since we can now write the overflow check in this one method rather than two different places.

protected static int minutesOf(long inSeconds) {
  return (int) (inSeconds / 60 % 60);
}

protected static int secondsOf(long inSeconds) {
  return (int) (inSeconds % 60);
}

protected static int hoursOf(long inSeconds) {
  if (inSeconds / 3600 > Integer.MAX_VALUE) {
    throw new ArithmeticException("result cannot fit in type");
  }

  return (int) (inSeconds / 3600);
}

Now we can rewrite the offending methods and constructor to use the common functionality provided by the abstract base class:

// in CompactDuration:
  @Override
  public String asHms() {
    return asHms(hoursOf(inSeconds),
                 minutesOf(inSeconds),
                 secondsOf(inSeconds));
  }

// in DurationImpl:
  @Override
  public String asHms() {
    return asHms(hours, minutes, seconds);
  }

  public DurationImpl(long inSeconds) {
    if (inSeconds < 0) {
      throw new IllegalArgumentException("must be non-negative");
    }

    this.seconds = secondsOf(inSeconds);
    this.minutes = minutesOf(inSeconds);
    this.hours = hoursOf(inSeconds);
  }

Not only did the refactoring allow us to eliminate redundancy but it made the code clearer as well. We could keep going, next by factoring out some error-checking code, but we are probably nearing diminishing returns. Here’s what we’ve ended up with:

One last UML convention: underlining means static.

1There are two potential points of confusion here: 1) Not every factory method is an instance of the factory method pattern, but rather those that work in the particular way introduced here. 2) We are declaring our factory method to be abstract, but the abstract factory pattern, which we will see later, means something else more specific.