6

I'm about to write a Java library. Basically, this library provides something like this to its user:

interface Foo {
  void doA();
  boolean aWorked();
  void doB(int value);
}

The user is not supposed to implement this interface (obviously). Thus, user code will look something like this:

Foo f = Library::SomeFactory();
if (someDecison()) {
  f.doA();
  if (!f.aWorked()) {
    f.doB(21);
  }
} else {
  f.doB(42);
}

I can make sure that neither preconditions nor postconditions of the methods in the interface change in the future. But there might be a new method, say doC().

Now, various resources including Orcale's Docs suggest that just adding this doC() to above interface is a bad idea. They propose different solutions from extending the interface ...

interface Foo2 : extends Foo {
  void doC();
}

... to blowing the code up with command patterns and what not. But the supporting reason is always that "all implementing classes must be changed". That's not an issue in my case, as all implementing classes are "under my control" and will need to be changed either way (when there's a doC()).

Is simply adding the method to Foo really a bad idea? And if so, why? Is there anything that I'm not taking into account here? My primary goal is to not break any user code written against that interface.

// That's what I'm planning
interface Foo {
  void doA();
  boolean aWorked();
  void doB(int value);
  void doC();
}

This source supports my feeling that it can be as simple as that:

[..] If the method is added to a class (interface) which Clients are not allowed to subclass (to implement), then it is not a breaking change. [..]

  • If all the classes implementing the interface (that you know about) are under your control, and you know that you are going to have to go through and update them all, what is the issue with following the suggestion in Oracle Docs? That is, if you create a Foo2 interface that extends Foo, updating the appropriate classes to implement Foo2 should not be a big deal, no? – Matthew Flynn Oct 28 '16 at 14:48
  • @MatthewFlynn indeed, but I fear that the external interface of the API then gets possibly confusing to the users. I need to say that these users are all going to be relatively inexperienced students. I'm currently considering putting the interfaces in "versioned packages", e.g foo1.Foo, foo2.Foo, .. perhaps I'm also over thinking this. – Daniel Jour Oct 28 '16 at 14:53
  • Will that always be how the client executes the code? Will there be code in-between f.doA(); if(f.aWorked()) f.doB(21);? Or will those statements always execute one right after the other? – Vince Emigh Nov 4 '16 at 1:36
  • Why not just add a warning in the JavaDoc: "This interface is provided solely for communication with this API. The author reserves the right to extend the interface at any time, so your code will break if you implement this interface." – BobDalgleish Nov 4 '16 at 17:14
4

There is no Java mechanism to prevent clients from implementing an interface that is accessible to them. The text you're quoting says "is not allowed to implement". Sadly, the only way that can be true (enforced) is if the interface is inaccessible to the client.

There are reasons a client may want to implement that interface; for example, if they are trying to bring together several different libraries, and they find your interface to be a good abstraction to reuse.

You can caution your clients against implementing your interface -- that your intention is to extend it, or, you can use Java's new default methods when you do extend the interface (or both).

  • At first I wanted to comment that there's no rational reason (or gain) for a client to implement the interface on its own, but then I thought about testing/mocking, implementing some form of logging, network bridging, other uses for the decorator design pattern, ... So I guess there are indeed valid reasons for the client to implement this interface on its own. – Daniel Jour Oct 27 '16 at 1:08
3

Best practices are fine and dandy, but it's important to be pragmatic.

In this case, I see nothing wrong with extending your interface in-place in the future. Like you mentioned, the only affected classes are in your control.

Also, why have a aWorked() member if you can simply have doA() return a success/failure result?

  • Thank you for your answer. That's what I was hoping for. Oh: That aWorked is just an example for a "query" method. Indeed, if this were real code then doA should just return a bool :) – Daniel Jour Oct 26 '16 at 23:32
  • 2
    @DanielJour That's the problem with foobar examples. People take them literally. It would be nicer to give a little more domain context (even invented for the answer but similar to the real problem) instead of the trite foobar examples. – Tulains Córdova Nov 2 '16 at 13:19
1

Evolving an interface without breaking code

Any new methods should supply default behavior, which is an easier (and cleaner) alternative to defining a new subtype.

Why is adding a new abstract method to an interface bad?

It does exactly what you want avoid: break code. If clients used a newer version of your framework for bug fixes, they'll be forced to declare the new method in whatever types implement the interface.

Preventing clients from implementing your interface

Even if Java supported this, which it doesn't, I would advise against it. You would limiting the usage your framework.

If your framework lacks an implementation the client may require/want, they (or anyone else, maybe a more experienced developer) should be able to extend upon Foo to expose new useful implementations that you may not have had the time to write, or maybe never even thought of.

  • -1 for the "Any new methods should supply default behavior" suggestion. You are trading compilation errors for some arbitrary behavior when some code calls doC on a class that did not implement doC even though it needed to because the compiler did not point you to it because doC had a default implementation, and you forgot about that class' existence so at no point did a programmer looked at that class and made a choice whether or not it needs to have it's own implementation for doC. – Idan Arye Nov 4 '16 at 3:03
  • @IdanArye How is the behavior arbitrary/random if there's a contract? If you wanted/needed to extend the contract for implementation specific behavior, you could override the method. Yeah, there's no error forcing your to declare the method, but that's because an implementation (that abides by the contract defined in the parent type) already exists. The only reason a dev would not implement doC() is if they didn't need to extend the contract, or if they didn't understand how the parent type works (didn't read contract). default removes the need to modify code if not needed (bug fix update) – Vince Emigh Nov 4 '16 at 3:38
  • There is another reason for a dev to not implement doC - if doC did not exist when they implemented the class. Using the metaphor, this is similar to adding new clauses to a signed contract without notifying the other party. Even if you are legally allowed to, you can't realistically expect them to respect these new clauses. Sometimes you don't need them to(e.g. the methods on streams), but if you insist on providing a default implementation for any new method you'll have to add stub implementations for the cases that are not sugar for utility methods - and this rarely works well... – Idan Arye Nov 4 '16 at 4:29
  • @IdanArye Clients aren't forced to use the method. This is primarily for backwards binary compatibility. Otherwise, anyone using the framework would either: 1. Be forced to cange all current uses of Foo to Foo2 to use the new functionality (scale up) 2. Be forced to modify their code just to make sure it compiles, even if they don't care to scale up by using the new functionality. Maybe they just want the optimizations and bug fixes that are bundled with the framework updates. – Vince Emigh Nov 4 '16 at 4:43
  • @IdanArye It definitely works. I'm sure you've used Collection#stream() before. Without defaults, youd be forced to use a different type (List2) to use stream(). If you wanted to implement stream() in your code, you'd have to change List to List2, rather than just calling it directly from the existing List. As more revisions are made, you end up with List5, if not worse. – Vince Emigh Nov 4 '16 at 4:50
0

If the interface is not supposed to be implemented by the client, you should make it package-private so clients will not even know it exists. If it defines a type that is part of your public API, you could make it a superclass of your own implementations. The superclass could be abstract or have a package-private constructor, and the implementations could be closed to extension by declaring them final or restricting access to their constructors.

You can always remove these restrictions later if you discover a compelling reason to do so. But you can't add them later without risking breakage in third-party code. So it's good practice in a public API to limit the visibility and extensibility of everything as much as possible, and open it up later.

  • If Foo was package-private, users wouldn't be able to call methods from it. f.doA() would cause a compiler error. – Vince Emigh Nov 4 '16 at 4:05
  • @VinceEmigh I missed that the OP's code sample represents user code. – Kevin Krumwiede Nov 4 '16 at 5:25

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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