API Design: Provide Sensible Defaults

Jakob Jenkov
Last update: 2014-05-25

One of the API design guidelines I try to adhere to is to provide sensible defaults. As far as I remember that advice originates from Joshua Blocks book "Effective Java". By "defaults" is meant, that if a certain parameter value, interface implementation, or subclass is used most of the time, provide a method that doesn't take that parameter. Instead it should use that value internally. In other words, "hardcode" it.

An API typically consists of various components and methods. One such method could be:

public class MyClass {

  public void readStream(InputStream stream, boolean closeStreamAferReading){
     ...
  }
}

If in most use cases the users just want the InputStream closed after calling the readStream() method, then most of the time the users will pass in true in the boolean parameter. Rather than burden the user with that, provide a convenience method, like this:

public class MyClass {

  public void readStream(InputStream stream){
      readStream(stream, true);
      }
  
  public void readStream(InputStream stream, boolean closeStreamAferReading){
     ...
  }
}

Provide Default Dependencies

The same is true for dependencies used internally. Say you have some interface called MyDependency which MyComponent needs an implementation of. Here is how that could look:

public class MyComponent{

  protected MyDependency dependency = null;

  public MyComponent(MyDependency dependency){
    this.dependency = dependency;
  }
}

If the same implementation of MyDependency is used most of the time, it can be a good idea to provide a default constructor that initializes the internal member dependency with that implementation. Say the implementation is called MyDefaultImpl, then the constructor could look like this:

public class MyComponent{

  protected MyDependency dependency = null;

  public MyComponent(){
      this.dependency = new MyDefaultImpl();
      }

  public MyComponent(MyDependency dependency){
    this.dependency = dependency;
  }
}

Just keep in mind that this default constructor creates a hidden dependency on the MyDefaultImpl class, from the MyComponent class. In many cases, this dependency is harmless though. Dependency injection fanatics may disagree with this. They would claim that you should mark the default implementation with an annotation stating it is the default implementation, and then have the DI container inject the default implementation when you ask the container for a MyComponent instance.

I am a big fan of dependency injection myself. I have even developed one of the more sophisticated dependency injection containers available for Java, the Butterfly DI Container. In my experience though, as long as the dependencies do not have side effects, like requiring some JAR file to be present on the classpath which isn't already present, a dependency like the one shown here will most likely not cause any problems. If you really need something else than the default implementation, the second constructor allows you to plug that implementation in. Thus you are never stuck with the default implementation. There is no reason to be over-religious about decoupling dependencies. Remember, looking at the constructor and seeing only a version that takes a MyDependency instance, can also be confusing, if you do not at the same time mention which implementations exists of MyDependency, and which is the default implementation.

Jakob Jenkov

Featured Videos

Java ConcurrentMap + ConcurrentHashMap

Java Generics

Java ForkJoinPool

P2P Networks Introduction

















Close TOC
All Tutorial Trails
All Trails
Table of contents (TOC) for this tutorial trail
Trail TOC
Table of contents (TOC) for this tutorial
Page TOC
Previous tutorial in this tutorial trail
Previous
Next tutorial in this tutorial trail
Next