API Design: Provide Sensible Defaults
Jakob Jenkov |
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.
Tweet | |
Jakob Jenkov |