You know the drill. You design a perfect framework, release it to a group of programmers, and receive rave reviews. Yeah! Then you start version 2, but discover all those happy customers are actually using your API in ways you never intended…or imagined.
Sure, it’d be nice to rename that method…but you cannot, because that will break someone else’s app. Every time you change something, other programmers have to update their own code, which costs money and time. Features quickly become frozen and virtually impossible to change. You now have legacy code. What happened?
Minimize and Segregate
This article examines two goals when sharing code. First, you should make the public API as small as possible. The fewer things you share, the less tightly coupled clients are. If you share a public API with 500 members, clients have 500 potential connection points to your framework. An API with 20 members has far fewer couplings with client code.
Second, you should segregate public code from “private” code. Make it very clear which parts of your framework clients are supposed to use. In some cases, you can introduce barriers that make it harder for clients to accidentally use internal implementation details.
Hopefully you can guarantee stability with a very small, well-segregated public API, and retain some freedom to improve hidden implementation details behind the scenes.
Minimize Visibility
We’ll start with an easy one. Rather than blindly writing public getters/setters for every field, only expose what you really need. Use the private keyword liberally; only use public for things that need to be available to callers.
Basically, minimize visibility whenever possible. This helps keep your public API small.
Hide Behind Interfaces
Interfaces provide another mechanism to hide internal implementation details. Programmers commonly define interfaces with accessors:
public interface Name {
String getFirst();
String getLast();
}
As you can see, the Name is immutable. We don’t see constructors in interfaces. Interface-heavy APIs play nicely with dependency injection tools like Guice or Spring and can significantly reduce the need to code directly against implementation classes.
Use an impl Package
Interfaces need implementations. Where do these go? And what about “helper classes” like LangUtils or ComparisonHelper?
I recommend putting implementation details into an explicitly-named impl package. This gives a clear cue that everything in that package is an implementation detail that may change in future releases.
Keep in mind that Java packages only provide a hint to programmers. It is very likely that many classes in your impl package need to be public in order for other parts of your framework to use them.
Provide Static Factories
So how do you encourage people to only use public interfaces but avoid classes in the impl package? You could encourage them to use dependency injection. Or, you can borrow a technique from the Glazed Lists project. Check out the GlazedLists class.
This is a class in the “public” package with static factory methods that return interfaces. Internally, however, these factory methods construct implementation-specific classes from non-public packages.
Although this does not guarantee programmers avoid implementation details, it does provide an officially-supported mechanism to get to the implementations.
Use Exotic Class Names
Taking the impl package one step further, you might want to name implementation-specific classes using awkward names like _MyApiImpl_LangUtils. It is quite easy for programmers to inadvertently include an impl package in an import statement.
But seeing a butt-ugly class name like _MyApiImpl_LangUtils directly in the source code makes it significantly more obvious that programmers are using something they should avoid.
Include Weird Methods in Interfaces
While it is usually good to encourage interfaces, sometimes you might prefer people extend an abstract base class instead. As soon as you add a method to an interface, everybody implementing that interface is broken.
However if people extend a base class, you can add new methods to that base class without (usually) affecting people.
For Example…
Check out the Matcher code from the Hamcrest project:
public interface Matcher<T> extends SelfDescribing {
boolean matches(Object item);
/**
* This method simply acts a friendly reminder not to implement
* Matcher directly and instead extend BaseMatcher. It's easy to
* ignore JavaDoc, but a bit harder to ignore compile errors .
*
* @see Matcher for reasons why.
*/
void _dont_implement_Matcher___instead_extend_BaseMatcher_();
}
Another Reason…
I just used this technique for another reason. I’m writing a little P2P framework and each node in the network has an Address, which is an interface. Behind the scenes, however, the framework has to downcast to an implementation-specific subclass of Address. Without getting into too much gory detail, adding a huge ugly method to the interface helps remind programmers to get their Address from my framework, rather than by implementing the interface directly.
Require a Key
This is a little hack I’m quite fond of. Suppose you have a public factory:
public class MyFactory {
// public class, but private constructor
public final class Key {
private Key() {}
}
public static Widget createWidget() {
return new WidgetImpl(new Key());
}
}
Let’s study that for a bit. The Key has a private constructor, so MyFactory is the only class that can construct it. This forces programmers (unless they use reflection tricks) to go through our createWidget() method to construct new widgets.
Here is how you’d write the WidgetImpl class, ideally in an impl package:
public class WidgetImpl implements Widget {
public WidgetImpl(MyFactory.Key key) {
if (key == null) {
throw new IllegalArgumentException("Please use MyFactory");
}
}
}
Since you cannot construct a Key (again, without reflection tricks), you must use the factory to construct new Widget objects.
(my apologies if the above has a typo or something…I’m coding in WordPress…)
Not the JarJar from Star Wars…
You might consider a tool like Jar Jar Links to hide the fact that your framework uses some specific third party JAR file.
Summary
I hope you find some of these techniques useful. Some people will always try to bypass your public API and rely on implementation details. Who among us has NEVER used some com.sun.* code in our own apps? Ahem…moving on…
The point is that framework developers need to make it very clear which interfaces, classes, and packages are considered “stable” and part of the “public” API. Good frameworks will make every effort to maintain stability of these public APIs from release to release.
Although you could rely on JavaDoc comments or simple package names, it is better to make it as hard as possible for programmers to accidentally rely on internal implementation details. Code completion in modern IDEs makes it really easy to accidentally import something from an implementation package.