2

"Code to interfaces" is considered good practice. Such code is easy to unit test and enables loose coupling. Users only know the interfaces and the onus of wiring concrete objects is upon the top-most level (this can be done in some init code or with the help of frameworks).

My question is about following the practice of code to interfaces: does it imply that a concrete class can never declare any public method which is not present in its interface?

Otherwise, it will force users to depend upon the concrete implementation. This will make such methods difficult for unit testing; if the test fails, determining if it failed due to an issue in the caller code or due to the concrete method will require extra effort. This will also break the Dependency Inversion Principle. It will induce type-checking and down-casting, which are considered bad practice.

jaco0646
  • 15,303
  • 7
  • 59
  • 83
nits.kk
  • 5,204
  • 4
  • 33
  • 55
  • 2
    Never say never, but yes it's a bad practice because you'll have the slicing and casting problem. – duffymo Apr 02 '16 at 18:07
  • 1
    Occasionally it's necessary (e.g. `ArrayList.trimToSize`). – Paul Boddington Apr 02 '16 at 18:08
  • @duffymo I liked 'Never say never'... whats the way out of it, or its never say never and just do it as we have good principles like KISS.. – nits.kk Apr 02 '16 at 18:08
  • 1
    No way out of it. If you must, you must. If you can avoid it, you shouldn't. There's no magic. I just don't like the dogma. If there's a time when you have to do it, just do so knowing the consequences. Every JDBC driver developer, like Oracle, has to provide the methods in the interfaces. But they usually provide extensions that are special to them. It's a form of lock in: If you use their extensions, you can't switch databases easily. – duffymo Apr 02 '16 at 18:10
  • @duffymo, I like that..I m almost convinced, but I have one more concern. What about the unit testing of the users who would be using this. The part using this will not be unit testable as if the test fails then it is not sure if its the user code that has issues or the concrete implementation. – nits.kk Apr 02 '16 at 18:14
  • One example is an `Interface` that only provides accessor methods. A concrete implementation might supply setter methods, so at the point where the Object that implements the interface is created, it is possible to set variables. In this case, the concrete implementation has methods that are not needed by the Interface, but without which setting the object state is more difficult. – KevinO Apr 02 '16 at 18:15
  • 1
    Your unit test will have the same problem that all clients do: You have to use the concrete type with the extra methods if that's what you're testing. In that case test the implementation, not the interface. Know the rules; break the rules knowingly. No magic. I'm not convincing you of anything. I'm saying that you already know the answer. – duffymo Apr 02 '16 at 18:16
  • yes duffymo, I actually do this, and guess many others (may be all of us) do this, but I read another question which got me thinking about this issue. Anyways its great to read the way you answer, "Know the rules; break the rules knowingly. No magic. " is again which got me impressed. thanks – nits.kk Apr 02 '16 at 18:19

2 Answers2

5

That is totally acceptable provided that the new methods aren't crucial to the operating of the class, and in particular to how it functions when someone thinks of it as the superclass or interface.

ArrayList provides good examples. It has methods that let you manage its internal memory, like ensureCapacity(int) or trimToSize(). Those are sometimes helpful if you know you're working with an ArrayList and need to be more precise about memory allocation, but they're not required for the basic operation of the ArrayList, and in particular, they're not required for having it operate as a general List.

In fact, interfaces themselves can add new methods in this way. Consider NavigableSet, which extends Set. It adds a whole bunch of methods that rely on the ordering of the set's elements (give me the first, the last, a subtree starting from here, etc). None of those methods are defined on Set, and even the fact that the elements are ordered isn't defined by the Set contract; but the Set methods all work just fine without the additional methods and ordering.

The advice to "code to the interface" is a good start, but it's a bit over-generalized. A refinement of that advice would be, "code to the most general interface that you need." If you don't need ArrayLists's methods (or its contract, such as its random-access performance), code to List; but if you do need them, then by all means use them.

yshavit
  • 42,327
  • 7
  • 87
  • 124
  • NavigableSet is an interface. So it would not create issues with unit testing as stubs can easily be created and no need to depend upon concrete implementations like TreeSet. Anyways I feel we should know the rules, strive to code as per the rules and at places where we need to break them, we must break with proper respect to the principles. We should know the consequences of the same, and if possible should take appropriate measures to avoid any ill-effect due to code changes in future – nits.kk Apr 02 '16 at 18:55
  • My point was that if you code to the NavigableSet interface, then you're not coding solely to the Set interface. And that's totally fine. :) – yshavit Apr 02 '16 at 19:07
  • 1
    I added a paragraph at the bottom to add why it's okay to use those extra methods. – yshavit Apr 02 '16 at 19:27
  • Yes yshavit, I read them, its always great to interact with so much knowledgeable people in the community and know their view points. Such discussions always give clarity of the things. Thanks for the answer. – nits.kk Apr 02 '16 at 19:37
0

@yshavit's third paragraph hits it right. Implement an extension of the "not enough" base interface, as exampled with public interface NavigableSet<E> extends SortedSet<E> (which, BTW, extends Set<E> extends Collection<E> extends Iterable<E>).

It's his second paragraph that troubles me. Why have "non-crucial" methods of the API that are not surfaced in some interface being implemented? In the ArrayList example, why not have the size management methods declared in an interface? Perhaps ManagedSize which would describe clear behavior for ArrayList (and other) classes to implement, along with the several other interfaces it implements (my JRE source says: public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable).

With such an approach, there is no need to decide which methods are "non-crucial," only to be surprised by some client code that depends on things like ensureSize to help avoid relocation during a time-critical phase, or trimToSize to release excessive overalloaction when it's algorthmically known that further growth will not be needed. Not that I'm promoting such algorthms as best practice, but even non-functional "behavior management" methods deserve their place in the light.

Finally, while I agree with sentiment of "Know Where the Lines Are, and yet Color As You See Fit" it doesn't give practical guidance. Here's attempt at such:

  1. Always start by coding to an interface, ie. all concrete public methods should be declared in an interface:
    1. Use multiple interfaces as needed
    2. Each interface should partition the implemented API into coherent non-overlapping aspects, e.g. List, RandomAccess, Cloneable, Serializable
    3. Tend to start with larger scoped interfaces and break them up as the design develops (before coding ala Waterfall, or as code evolves ala Agile); interfaces are one of the easier design artefacts to refactor.
  2. If a given interface you are implementing is "insufficient":
    1. Extend the base interface and add the methods you need, then implement that one, OR
    2. Create an augmenting interface (like the ManagedSize idea, above) with just the additional methods and then implement them both
  3. Only when you find you can't do that, then relax only as much of the rule as you need to make things work (often, this will be an experimental trial-error "does it work, yet?" cycle).

Reasons for #3's "can't" will vary, but I expect them to be external to the application design, e.g. the ORM I'm using becomes confused, the IDE plug-in doesn't refactor it correctly, the DSL translator I'm forced to use fails when a class implements more than three interfaces...

jaco0646
  • 15,303
  • 7
  • 59
  • 83
Stevel
  • 631
  • 5
  • 13
  • 1
    "In the ArrayList example, why not have the size management methods declared in an interface" -- because it doesn't apply to all implementations. A LinkedList doesn't need it, for instance, or a CopyOnWriteArrayList. So, rather than having the interface declare the union of all possible methods (and what if a third party comes along and needs a method you haven't thought of?), Java has a system where it's fine to add new methods on top of what the interface declares, as needed by the implementation. – yshavit Apr 02 '16 at 21:27
  • ISP is a good design principle which will be breaking if all the methods are added in the List interface. List should only have methods needed by the concrete implementations of List. Another point is we should have an interface when we need the toggle effect, i.e. if we have to switch between two similar types then we should define the interface with the common RELATED methods. – nits.kk Apr 03 '16 at 05:28
  • @yshavit - But `ArrayList` is a specific implementation that is exposing additional API (e.g. `ensureSize` and `trimToSize`). I'm suggestion that those two (and perhaps other related allocation management) methods be declared in an interface, which might even be useful in other contexts. Other impl's that don't want to align with such a segregated interface are under no obligation. Thus, `LinkedList` wouldn't implement it, but perhaps others might. Like you, I don't want interfaces (or classes) to declare "The Mother of All API Unions," but let's partition what is declared with interfaces. – Stevel Apr 03 '16 at 05:46
  • Oh,I misunderstood -- I thought you meant put those on the List interface. I think your approach would lead to an explosion of interfaces (like a Foo interface with just a single FooImpl implementation), which can reduce readability... but that's a big discussion, not fit for a SO comment thread :) – yshavit Apr 03 '16 at 15:58