9

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
  • 1
    I'm interpreting "program to an interface" to mean... I don't want to put words in their mouth but from reading the answer and the comments, I don't believe that's what Telastyn meant at all: Here, "programming to an interface" means focusing your design on what the code is doing, not how it does it. ... NO. List/ArrayList is not what I am talking about at all.. Whether your interfaces should be classes or interfaces is a different question entirely, but I'll point out that they're not interchangeable because interfaces can't have methods that inspect two instances of a class.
    – Doval
    Commented Oct 3, 2014 at 13:39
  • An interface isn't "more abstract" than a class; it's a different, complementary kind of abstraction. Unfortunately Java classes are crippled by the fact that you can't switch one for another with an equivalent API, but that's something you have to live with. Maybe it's the overly long names but this seems like a really convoluted way to try to get around that limitation.
    – Doval
    Commented Oct 3, 2014 at 13:41

4 Answers 4

4

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:

  1. 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.
  2. What would prevent your coworkers from creating a class in the trusted package and invoking the constructor there?
1
  • Good points! 1) My particular circumstance only has one project and we regularly recompile the whole thing when we start work on a stream. But this might not be the case for other developers reading this post. 2) The java docs. I don’t like where things go if we don’t trust the package. The constructor becomes private. The static factory method uses reflection to get an instance. Copycat developer uses reflection and locks us down to the class. At this point I start thinking about throwing an exception from the constructor if I don’t like who’s above me in the stack trace. Yuck. Commented Oct 4, 2014 at 23:47
4

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
  • This answer, while appreciated, fails to justify why its approach should be preferred and ignores the questions concerns. Please address any issues with the approach detailed. Indeed, Dependency Injection is more than the Strategy Pattern. However, ArrayList has the List interface and people still code against ArrayList. So "Just make a java interface for every class" isn't offering protection from bad coders. As far as I can tell Dependency Injection works fine with the questions approach. This is not “giving up on interfaces”. It’s trying to make default interface methods in java 1.6 Commented Oct 4, 2014 at 1:48
  • @CandiedOrange "Proper" behavior 99% of the time is to code against List, not ArrayList. Your methods should accept Lists, not ArrayLists. I'm not sure in your comment if you are suggesting otherwise.
    – user949300
    Commented Oct 4, 2014 at 3:59
  • @user949300 Not suggesting otherwise at all. But providing an interface does not guarantee use of that interface. This isn't even the main point. The main point was to find a way to not provide a java interface while still preserving the right to provide one later. Commented Oct 4, 2014 at 6:44
  • 1
    You can force people to use the interface mostly by having a public factory and a private implementation. It is actually a lot cleaner than the solution in the question. I think the main cited problem in the question, programmers not liking getting the interface instead of the implementation, is all handled by IDEs nowadays anyway. Android Studio certainly lets me go direct to the implementation, and even the other way around, when I search for usage of a method from an implementation it offers to use the interface if I want. Commented Oct 5, 2014 at 0:45
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 Apples, 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:

  1. 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.
  2. 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 in Apple, 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.
  3. 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 Apples and Oranges)? 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
  • Thank you for a well-considered answer that keeps people from programing to the implementation. However, it is also a "just make a java interface for every class". The question proposes an alternative idea: preserve the right to be a java interface. A java interface isn’t something you get for free. Refactoring tools help but you still end up defining method names and signatures in two locations. YAGNI says if you don’t need it now don’t make it now. What this answer fails to do is explain why the java interface is needed now. Did the preservation attempt fail? Commented Oct 4, 2014 at 6:14
  • @CandiedOrange You may say that you aren't going to need interfaces right off the bat, but you aren't applying YAGNI to all the viewpoints. First, you say your co-workers like to jump into your method implementations. Why? YAGNI. By keeping them to an interface, you keep implementation details out of their field of vision. Second, you assume that interfaces are always going to have an implementation. YAGNI. What if we want to see if and how a class might be used. Not used? Don't implement. Third, when your co-workers need to test out their units, they can create mocks/stubs with an (contd)
    – cbojar
    Commented Oct 4, 2014 at 15:23
  • @CandiedOrange interface. Implementation for testing? YAGNI. You also assume that your classes and their future interfaces will align, but classes may have lots of extra parts that YAGNI in an interface. By doing the interface first, you can see what is truly interface and what is implementation. You don't need to provide an interface for every class, just the classes that might get used elsewhere (which should not be every class. They AGNI). Anything else you should hide in the package, and you can do whatever you want.
    – cbojar
    Commented Oct 4, 2014 at 15:28
  • Again I ask, did the preservation attempt fail? I’m not saying you have to do this. I’m exploring the potential of starting with a simple class that lets me procrastinate making the decision of what exactly it should be. Please explore it with me. If your testing environment demands the client be programmed to Java Interfaces (some don’t need that to test in isolation) then you have a case for making the Java Interface when you test. However, if you go to test and run into a class with no Java Interface wouldn’t you prefer that it was the kind that let you turn it into a Java Interface? Commented Oct 4, 2014 at 19:32
  • @CandiedOrange Can it, in theory, work? Yes, but with significantly more effort (and much higher risk) than making an interface upfront. Using a (published) class requires more diligence. You need to keep the public API clean, and to separate any implementation concerns. And when you go to extract an interface later, you must do more design work to determine what is actually part of the interface, what is not, and what has become intermingled. And for me, having an interface is far more useful than having a class, esp. if it is not my class. It is more open-closed, and safer from new bugs.
    – cbojar
    Commented Oct 5, 2014 at 5:12
3

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
  • Finally, an answer from the other side of “just make a java interface for every class”. Re too many methods: whatever the class exposes is part of the interface/contract. Direct construction is prevented by access modifiers. DI isn’t required. A service locator or factory method also works. To the clients, interface and implementation appear to be separated even though they aren’t yet. I don’t understand the “more than 1 interface” concern. We can talk here chat.stackexchange.com/rooms/17620/proto-interface I’ve decided to call this a Proto Interface. Commented Oct 4, 2014 at 19:54

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.