'Should callers of an interface (need to) make assumptions about the underlying implementation?

I understand the benefit of Programming to an Interface and the benefit of using an Interface for the return type of a method:

public List<Integer> getUserIds() {
    return Arrays.asList(1, 2, 3);
}

I wonder if this is still applicable when the method callers rely on a particular implementation to function correctly? For example,

public static void main(final String args[]) {
    final Map<Integer, String> map = getUserIdMap();
    Set<Map.Entry<Integer, String>> entrySet = map.entrySet();

    // print user name in the order of their Ids
    for (final Map.Entry<Integer, String> entry : entrySet) {
        System.out.println(entry.getKey() + ", " + entry.getValue());
    }
}

public static Map<Integer, String> getUserIdMap() {
    final Map<Integer, String> s = new TreeMap<>();
    s.put(3, "Tracer");
    s.put(2, "John");
    s.put(5, "Jane");
    s.put(6, "Jenny");
    s.put(1, "Rob");
    return s;
}

In this case, is it better for getUserIdMap() to return a SortedMap instead of Map so that the method caller does not need to guess the map's underlying implementation? Or is it generally better to return a Map because the method entrySet() belongs to Map?



Solution 1:[1]

It would be bad for client code that uses an interface to depend on an implementation detail that is not documented on the interface.

In your example, the Set and Map interfaces make no guarantees about the order of the elements inside them, so it would be bad for the client to make assumptions about them.

But this is not a problem in your case, as there are more specific sub-interfaces of Set and Map that do make guarantees about order. They are java.util.SortedSet and java.util.SortedMap. If your client code uses these interfaces, then they are allowed to make assumptions about order.

TreeSet and TreeMap implement these interfaces, respectively.

From the Javadoc for SortedMap:

A Map that further provides a total ordering on its keys.

Solution 2:[2]

The tradeoff

Your example is a case of trading off the method designer's freedom against the method user's freedom.

For the method designer, it is nicer to return Map, so that she can change her mind later with respect to the implementation if a better approach comes up.

For the method caller, it is nicer to receive a SortedMap, so she can rely on the contents to be sorted rather than having to sort them herself.

How to make it

The proper way to think about the situation (for the method designer) is to try and foresee the future:

  • Will I likely want to later change my implementation to use a non-sorted Map?
  • Will my caller likely want to receive a sorted result?

If the answer to the latter question is yes, this is likely the more important of the two, because data structures of implementations are changed only rarely in practice -- but inconvenience to the callers is, um, inconvenient.

How to break it

The worst possible outcome is that both participants go their preferred way at the same time: The designer only guarantees Map, but the caller still relies on receiving a SortedMap. Modularization is broken.

Do this in enough spots in a large system and you will have lots of trouble when you really need to change an implementation.

Moral: As a designer, make as many guarantees to your users as you can -- but no more.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Erwin Bolwidt
Solution 2 Lutz Prechelt