Java 8 default methods can be a handy tool for refactoring interface contracts. You can use this trick in case you want to add a new interface method that can be expressed in terms of an existing one. You don’t want to change the implementors of the interface (at least for now).
Let’s take an example on a interface (Loader
) with a mis-defined contract. Instead of getting levels where a Loader
can be applied to, I wanted the loader to answer this question directly.
The initial code was as follows:
public interface Loader { void load(Page page); List<Level> getLevelsToBeAppliedOn(); } boolean shouldBeApplied(Loader loader, Level level) { return loader.getLevelsToBeAppliedOn().stream().anyMatch(level::equals); }
After the refactor, the Loader
interface gets a new default method, shouldBeAppliedOn
that delegates to the existing method, getLevelsToBeAppliedOn
:
public interface Loader { void load(Page page); List<Level> getLevelsToBeAppliedOn(); default boolean shouldBeAppliedOn(Level other) { return getLevelsToBeAppliedOn().stream().anyMatch(other::equals); } }
The client code is simplified:
boolean shouldBeApplied(Loader loader, Level level) { return loader.shouldBeAppliedOn(level); }
Now this method doesn’t make our intent any clearer and can be safely inlined.
This refactoring didn’t break the implementors of the Loader
interface. The next step would be to implement the shouldBeAppliedOn
method in all Loader
derivates. When done, I could convert the default method to an abstract method (i.e. remove its body) and remove the original method (getLevelsToBeAppliedOn
).