In java 1.8 they have wonderful new “default interface methods”. In 1.6 how close can we come? The goal: use code to keep clients from being able to tell that a class is not a java interface. If we preserve the right to refactor the class name into a Java Interface we don’t need to create a Java interface
now. We just provide the class
. Later, refactoring so the class name becomes an interface wont even touch client code.
From Telastyn’s answer in Understanding “programming to an interface”:
- The “Dependency Inversion Principle” says that an object shouldn’t control the creation of its dependencies, it should just advertise what dependency it needs and let the caller provide it. But it doesn’t specify whether the dependency should be a concrete type or an interface.
I want to take advantage of this lack of distinction. I also want to interpret the principle “program to an interface” to mean, “I shouldn’t be able to tell if any client code is working with a java Interface or a java Class”. So much so, that later someone could swap out the class for a java interface with the same name and no client code would need to be touched. That may seem overly strict and not exactly what was meant by these design principles (which try to be ignorant of java’s particular quirks) but I’d love to pull it off.
Here’s why: Our development environment doesn’t use continuous integration. Releases that force everyone to integrate their code happen once every… well it hasn’t happened in all the years I’ve been working there. Worse than that, testing is hugely manual for reasons I wont even attempt to justify. This means: writing a class that will get used by other people is essentially published because changing it will break someones stream or tested code that is closed and closed hard. This means: no refactoring the signature of anything public on a shared class. It feels the same as I expect writing an API for a library must feel.
I believe in YAGNI. I don’t want to do a BDUF. But this situation tempts me to do silly things like put a Java Interface (big I) on every shared class even though, for now, they only have one implementation. This is against YAGNI, it annoys developers who consider an unneeded interface to be clutter.
What I really want is the freedom to add that Interface, and maybe alternate implementations, later if needed. I want to be 100% sure I can do this even when I can’t even see all the code that might be written against my widely shared class. I can only be that sure if I use the compiler to FORCE users to program to my interface (little i).
The question: Can you break this protection w/o using reflection from outside the trusted package? Is the following the right way to achieve this? In this environment you only get one shot at getting this right. I want to know if there is anyway to lock me down to the old implementation from outside the trusted package. All the commented code is “future” code. Except for the errors in main, which should always be errors.
Here we see someone trying their best to lock us down to an implementation:
package untrusted;
import trusted.AmIAnInterfaceWithDefaultImplementation;
import trusted.WhoKnowsWhatImplementation;
public class Activate {
public static void main(String[] args) {
System.out.println( WhoKnowsWhatImplementation.thisProvides().letsFindOut() );
AmIAnInterfaceWithDefaultImplementation i =
WhoKnowsWhatImplementation.thisProvides();
System.out.println( i.letsFindOut() );
//Error: AmIAnInterfaceWithDefaultImplementation i = new AmIAnInterfaceWithDefaultImplementation();
//Error: The constructor AmIAnInterfaceWithDefaultImplementation() is not visible
}
//Error: public class ImGonnaForceYouToBeAClass extends AmIAnInterfaceWithDefaultImplementation {}
//Error: The type ImGonnaForceYouToBeAClass cannot subclass the final class AmIAnInterfaceWithDefaultImplementation
}
Prints: Default implementation
twice.
Here we choose the implementation to provide in a static factory method. It’s only because we’re in the same package that the constructor works.
package trusted;
public class WhoKnowsWhatImplementation {
public static AmIAnInterfaceWithDefaultImplementation thisProvides() {
return new AmIAnInterfaceWithDefaultImplementation();
//return new IAintNoInterfaceNoMore();
}
}
The class below provides both the interface (small i) and the implementation but because the only constructor is protected and the class is final nothing outside the trusted package can force the interface and implementation to stay together.
package trusted;
/**
* Please don't instantiate directly as this may become an Interface.
* Use WhoKnowsWhatImplementation
*/
public final class AmIAnInterfaceWithDefaultImplementation {
protected AmIAnInterfaceWithDefaultImplementation(){}
public String letsFindOut() {
return "Default implementation";
}
}
//public interface AmIAnInterfaceWithDefaultImplementation{
// public String letsFindOut();
//}
We’re free to switch to being an interface and push the implementation out to a new class
package trusted;
//public class IAintNoInterfaceNoMore implements AmIAnInterfaceWithDefaultImplementation {
//
// @Override
// public String letsFindOut() {
// return "Shiny new implementation";
// }
//}
The need for the ‘trusted’ package comes from a desire not to have the static factory method live on the same class it’s instantiating. It would have to move when we switched to a Java Interface which would impact client code. That is why the constructor had to be package accessible.
Can you lock this down as a class from outside the trusted package without using reflection? Is your way of locking it down something that is reasonable to protect against? What else would you do to protect against getting locked down? Is there a better approach in java 1.6?
I’d also be interested to know if there is anyway to protect against reflection beyond the java doc begging you not to instantiate it directly but I suspect there isn’t.
Many designs fail to adhere to all the design principles. This one violates the open closed principle: classes should be open to extension but closed to modification. However, this is an attempt to ensure that the classes clients can stay closed even if it changes into a Java Interface.
The example refactoring shown here switches to an interface
and implementation class
. It’s worth noting that instead it might be decided that the class should actually be abstract
. When we switch to java 1.8, it might even end up as an interface with default methods on it. The whole point is to preserve the right to make this decision later so we don’t have to make it now when we don’t know which to do and don’t really care.
EDIT: I appreciate all these answers. Before I accept one, what I’m waiting for is analysis of how vulnerable this approach is to getting locked down to being a class. I’m willing to accept one that shoots down this whole idea by showing me code that dooms it to forever be a class. Accepting one that claims it’s a sound idea is a nice ego boost but I’d really rather fail here than in the code base. So convincing me that it’s a sound idea is the harder job.
What I’ve learned from these responses:
- Converting from the class to the java interface requires that clients recompile
- The class must not expose any more than the java interface could (e.g., no static methods)
- Some will insist classes implement Java Interfaces upfront regardless of when they are needed, sigh
The need to recompile the client code strikes me as the most significant issue to be aware of so I’m accepting Maarten Winkels answer. Thank you all and happy coding.
2
I don’t see any way to “lock down the interface to a class” in your code, but I do think you need to consider two things:
- Your clients would still need to re-compile their code, even though the client source code doesn’t have to change: invoking a method on an interface uses a different bytecode operation then invoking a method on an instance.
- What would prevent your coworkers from creating a class in the trusted package and invoking the constructor there?
1
Creating an interface is the best solution to your problem, and your only arguments against them are:
-
You won’t ever have multiple implementations (YAGNI)
What I really want is the freedom to add that Interface and alternate implementations later if needed.
Interfaces are not just for switching implementations at run-time. Giving yourself ability to change the implementation behind an API without having to change client code is a valid reason to define a capital-I interface.
-
Your fellow developers find it difficult to navigate to implementations
(…) it annoys developers who like to be able to click on code and go right to the implementation not the Interface
It’s trivially easy to navigate to implementations of a given method call in Eclipse: hold Ctrl and hover over the method call; a dropdown will appear with the Open Implementation option.
I haven’t used IntelliJ in a while, but you can do it similarly there.
Don’t give up on interfaces so easily. Solving your problem is one of their primary benefits.
4
The correct solution is to use a combination of interfaces and simple factories. This is the general direction you are going, and is the correct way to go. To make this simpler, let’s use the analogy of fruit. (The names are a lot shorter.)
We have a Fruit
interface, which is in our trusted
package:
package trusted;
public interface Fruit
{
public void nomnom();
}
We have a single implementation of Fruit
, Apple
. We want the end user to be able to have Apple
s, but not be able to directly instantiate an Apple
:
package trusted;
final class Apple implements Fruit
{
@Override
public void nomnom() {
System.out.println(nom3() + ", apple!");
}
/* Not in Fruit */
public String nom3() {
return "Nom, nom, nom";
}
}
Apple
is also in the trusted
package. First, we note that Apple
doesn’t have a visibility modifier (public, private, etc.). This means that Apple
has the default visibility, which just so happens to be akin to package-private. This means only classes in trusted
can access anything about Apple
. Outside of trusted
, Apple
cannot be instantiated (even if it provides a public constructor!), and cannot have any static methods called on it. No one can know anything about Apple
. This creates a small problem in that we want clients to be able to get an Apple
if they want a Fruit
. They cannot directly create one, so we need to provide a FruitFactory
with public visibility inside the trusted
package:
package trusted;
public class FruitFactory
{
/* This could also be an instance method */
public static Fruit getFruit() {
return new Apple();
}
}
Now we have a way to get a Fruit
(an Apple
in this case) without exposing the implementation of Apple
. We can see that in action:
package untrusted;
import trusted.*;
public class Main
{
public static void main(String[] args) {
Fruit f = FruitFactory.getFruit();
f.nomnom();
}
}
And everything works! We can get a Fruit
without hassle. If we try to instantiate or access Apple
directly in any way from outside trusted
, we get a compiler error.
Now say we’ve come up with the newest and greatest in fruit technology, Orange
. Since Apple
is now old hat, we’re going to give everyone Orange
from our factory instead. First, Orange
:
package trusted;
final class Orange implements Fruit
{
@Override
public void nomnom() {
System.out.println("Nom nom nom, orange!");
}
}
And now, all the changes we have to make to get everyone on the Orange
bandwagon:
package trusted;
public class FruitFactory
{
public static Fruit getFruit() {
return new Orange();
}
}
Done. Notice that nothing outside of trusted
had to change. We didn’t touch Main
. We didn’t even touch Apple
. We added Orange
, and changed the factory class (the only non-open-closed change, but highly localized and isolated). It still all compiles just fine because we’ve had them program to the Fruit
interface rather than the Apple
implementation. All they need to know is to use the FruitFactory
to get a Fruit
, and don’t worry about the rest (the implementation details).
Notice that, because we are now programming to an interface, we don’t need to know what the implementation looks like. We should be able to build a client completely by looking only at the Fruit
interface, and never at Apple
or Orange
.
I said that you should definitely use interfaces, and not try to “fake” an interface as a class then try to change it later. There are a number of reasons for this:
- If you start with a class, you may end up writing it in such a way that the API is implementation-dependent. Writing an Interface (big-I), without having any implementation in front of you, frees you from thinking about how you are going to do something. Instead you focus on what you want to do.
- Writing Interfaces allows you to publish the least amount of information necessary. Interfaces don’t carry any implementation, so when you publish it to the rest of your team, all they know is the published API. If you publish an implemented class, it may have a larger API than you intend (note
nom3
inApple
, which we’d want to keep hidden), and once others start to depend on those extras, you can never change them, even if you only intended them to be implementation details. A published implementation may also have certain characteristics (performance, etc.) that may not be intrinsic to the API. Others may optimize for that implementation, only to run into trouble when you come out with something new. - By publishing the least amount necessary, you gain freedom. You’re always going to need freedom. With a private API, you have the freedom to just make a change, but since all your APIs are essentially published, you need to reserve as much freedom as you can get. Need to swap out an implementation at compile time (like our
Apple
s andOrange
s)? No problem. Need to switch out an implementation at runtime? No problem, just have our factory figure out what is needed and give that. Need to implement a feature that requires using another pattern, like decorator or adapter? No problem. Just implement the interface around whatever implementation you need. You don’t need to juggle a class-into-an-interface refactoring later, meaning you can be more open-closed as well.
You seem very concerned to make sure that others are technically incapable of using your components in unexpected ways. This accomplishes that, but remember that a determined person will always find a way to circumvent whatever you try to do, even to their own detriment. Document the easy path, show why it is the best. Most people will take the easiest path you provide, and you can sleep soundly.
8
First I take program to an interface to mean more of programming to a contract rather than
programming to a literal Java interface. Contracts can be implemented in classes or interfaces (or even functions).
I want to take advantage of this lack of distinction. I also want to
interpret the principle “program to an interface” to mean, “I
shouldn’t be able to tell if any client code (using code) is working
with a java Interface or a java Class”. So much so, that later someone
could swap out the class for a java interface with the same name and
no client code would need to be touched. That may seem overly strict
and not exactly what was meant by these design principles (which try
to be ignorant of java’s particular quirks) but I’d love to pull it
off.
We run into a similar issue at my workplace. When everything has an interface, I lose sight of what has multiple implementations and what doesn’t. I might not care about things that do have several implementations when writing code. But I might care when reading code or debugging code.
There are several potential problems with using classes first and refactoring to an interface.
One of the problems I can see is exposing too many public or package (default) access level methods. If the client can use public or package protected methods that aren’t part of the interface, you can’t guarantee that you can safely swap an interface in place for the class.
Theoretically a class is serving the purpose of an interface would if it only exposes methods to the outside that would be present in the interface. If you can be strict with your classes and only expose methods that would be in the interface, then I fail to see how you couldn’t swap them out.
Another problem would be a client using direct construction of the class.
However an interface doesn’t prevent me from doing this:
Foo bar = new DefaultFoo();
In order to truly be able to swap instances in and out, you would need Dependency Injection of some flavor. Also, you may still need to modify client code if you create multiple implementations. Something has to know if you wanted a FancyFoo or a PlainFoo for this client.
A final concern would be if the class really implemented more than 1 interface. You’ll find it a lot harder to swap an interface for a class if you have a Frobnicator that implements both the Foo and Bar interfaces. Clients using Frobnicator might be calling Bar functionality when they only needed a Foo, and vice versa.
1