4 min read
One of the new features of Java 8 is the concept of default methods in interfaces (1).
“Default methods enable you to add new functionality to the interfaces of your libraries and ensure binary compatibility with code written for older versions of those interfaces”.
This is excellent, you can now extend your interfaces without breaking current implementation (backwards compatibility). As great as this idea is, developers have found another usage for default methods: build tightly coupled systems that look loosely coupled.
Let’s say I’m building a couple of IoT cyborg pets to keep me company, a duck and an owl. My code would look something like this:
public class Duck {}
public class Owl {}
Now I want to add a common fly
behaviour to them, how do I achieve this? By inheritance, of course (2): I create a base abstract class, add the fly
behaviour there, and make my cyborgs to extend the base class:
public abstract class FlyingCyborg {
public void fly() {
//implementation goes here e.g. move wings
}
}
public class Duck extends FlyingCyborg { }
public class Owl extends FlyingCyborg { }
My cyborg pets can now fly
. Beautiful.
It would be great if they could also run
. But wait, I have a problem: I cannot add run
behaviour to the FlyingCyborg class, or I would break the Single Responsibility Principle (3), and Java doesn’t allow multi-inheritance. Oh no, horror!
Default methods to the rescue!
Yes, thank you Java 8! Now I can work around all these nasty impediments (4) and make my code great again!
Easy-peasy: I create an interface RunningCyborg with one default method run
, and I make my lovely pets to implement this interface. This way my cyborgs will inherit run
behaviour:
public interface RunningCyborg {
default void run() {
//implementation goes here e.g. move legs, or wheels
}
}
public class Duck extends FlyingCyborg implements RunningCyborg { }
public class Owl extends FlyingCyborg implements RunningCyborg { }
Voilà! My pets can now fly
and run
.
Now, they will have to sleep, won’t they?
public interface SleepingCyborg {
default sleep() {
//implementation goes here e.g. shut systems down
}
}
public class Duck extends FlyingCyborg implements RunningCyborg, SleepingCyborg { }
public class Owl extends FlyingCyborg implements RunningCyborg, SleepingCyborg { }
And so on, and so forth. Beautiful, isn’t it?
No, it’s not. It’s horrible! If you haven’t noticed it, this is me being ironic. This is wrong, really wrong. I have built a tightly coupled system (5). My cyborg pets are tightly coupled to the abstract class and to the interfaces. I cannot test them separately: mocking an abstract base classe is hard and how do I even mock a default method? (6). And how do I test it? My rule of thumb: if you cannot test it, don’t write it.
“Favour composition over inheritance”, in case you haven’t heard before (7).
As weird as all of this might sound, it’s happening right now. I’m currently working on a project where default methods are used this way. This leads to the testing ice-cream cone anti-pattern (8), which prevents sustainable development.
(1) Default methods
(2) No, of course not.
(3) Single Responsibility Principle
(4) SOLID principles
(5) Monolithic application
(6) There might be a way you can mock a default method, but you would at least need composition instead of inheritance
(7) Composition vs inheritance: how to choose
(8) The testing ice-cream cone anti-pattern (aka inverted pyramid of testing)