@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:
- Always start by coding to an interface, ie. all concrete public methods should be declared in an interface:
- Use multiple interfaces as needed
- Each interface should partition the implemented API into coherent non-overlapping aspects, e.g.
List
, RandomAccess
, Cloneable
, Serializable
- 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.
- If a given interface you are implementing is "insufficient":
- Extend the base interface and add the methods you need, then implement that one, OR
- Create an augmenting interface (like the
ManagedSize
idea, above) with just the additional methods and then implement them both
- 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...