Re: "Program to an interface" - When to break a design pattern

Discussion in 'Java' started by Steven Simpson, May 5, 2011.

  1. On 05/05/11 20:21, Zapanaz wrote:
    > So you have the method
    >
    > public LinkedHashMap<String, Integer> getSortedMap() {
    > //do stuff
    > }
    >
    > (not necessarily public)
    >
    > Now the design principle says, the method signature should instead be
    >
    > public Map<String, Integer> getSortedMap() {
    > //do stuff
    > }
    >
    > The problem is, where I'm creating sortedMap above, I need the map to
    > retain the insertion order. If what's returned actually is a Map,
    > rather than a LinkedHashMap,


    (I'll assume that you meant "If what's returned is not an implementation
    of LinkedHashMap...", rather than addressing the literal point.)

    > then the results the user actually sees
    > are going to be in the wrong order. Making things worse, in this case,
    > nothing would actually break, only the end user would notice anything
    > was actually wrong.
    >
    > So in this case, it seems to me, that using LinkedHashMap in the
    > method signature makes sense. The fact that the return retains the
    > insertion order is an integral part of what the method does.


    I would say that it was still better just to use Map, as LinkedHashMap
    need not be the only implementation with the predictable iteration that
    the caller expects. Additional documentation is then required, of course.

    Hypothetically, what you might want is an intermediate interface between
    Map and LinkedHashMap, e.g. InsertionOrderedMap, which extends Map but
    adds no methods. This might provide some sort of compile-time
    protection, that mere documentation cannot provide.

    However, IMHO, its value is dubious, for a few reasons:

    * An implementation of InsertionOrderedMap can be compiled without
    meeting the insertion-order requirement.
    * Another implementation of Map can be compiled without extending
    InsertionOrderedMap while meeting the insertion-order requirement.
    * Specifically for LinkedHashMap, it can actually provide access
    order instead, so it couldn't implement both InsertionOrderedMap
    and a similar AccessOrderedMap without breaking at least one
    requirement, whichever way a particular instance was configured.

    OTOH, this sort of thing is already done, with Set and Collection. Set
    extends Collection, but doesn't actually define any new methods. (It
    does take advantage of 'overriding' the documentation, though.) Is it
    worth having Set if:

    * you can implement Set without actually having set semantics?
    * you can implement set semantics with just Collection?

    So, if you're prepared to trust that an implementation of Set or
    InsertionOrderedMap follows the documented rules that can't be
    compiler-enforced, perhaps it's as well do away with those interfaces,
    and just put the documentation on methods like getSortedMap, and trust
    that they are implemented accordingly.
    Steven Simpson, May 5, 2011
    #1
    1. Advertising

  2. Steven Simpson

    Tom Anderson Guest

    On Thu, 5 May 2011, Steven Simpson wrote:

    > On 05/05/11 20:21, Zapanaz wrote:
    >
    >> So in this case, it seems to me, that using LinkedHashMap in the method
    >> signature makes sense. The fact that the return retains the insertion
    >> order is an integral part of what the method does.

    >
    > Hypothetically, what you might want is an intermediate interface between
    > Map and LinkedHashMap, e.g. InsertionOrderedMap, which extends Map but
    > adds no methods. This might provide some sort of compile-time
    > protection, that mere documentation cannot provide.
    >
    > However, IMHO, its value is dubious, for a few reasons:
    >
    > * An implementation of InsertionOrderedMap can be compiled without
    > meeting the insertion-order requirement.
    > * Another implementation of Map can be compiled without extending
    > InsertionOrderedMap while meeting the insertion-order requirement.


    These points are true of any interface! Look:

    public class DubiousComparator implements Comparator<String> {
    public int compare(String a, String b) {
    return 1;
    }
    }

    public class NotAComparator /* does not implement Comparator<String> */ {
    public int compare(String a, String b) {
    return a.compareTo(b);
    }
    }

    Interfaces are useful because they let you make a promise; alas, making
    programmers keep their promises is beyond any compiler.

    > * Specifically for LinkedHashMap, it can actually provide access
    > order instead, so it couldn't implement both InsertionOrderedMap
    > and a similar AccessOrderedMap without breaking at least one
    > requirement, whichever way a particular instance was configured.


    I think it would be sufficient to have an interface OrderedMap, which is,
    very roughly, to a Map what a List is to a Set. Except really, we would
    also want an OrderedSet. SortedMap/Set could perhaps be a subtype of
    OrderedMap/Set; or, borrowing from Smalltalk, we could have a
    SequenceableMap/Set, from which we derive different kinds of sequencing. I
    think these things would be useful, but not enormously so. I feel
    Zapanaz's pain, but i think it's just One Of Those Things.

    tom

    --
    Men? Women? Give me a colossal death robot any day!
    Tom Anderson, May 5, 2011
    #2
    1. Advertising

  3. Steven Simpson

    Jim Janney Guest

    Steven Simpson <> writes:

    > On 05/05/11 20:21, Zapanaz wrote:
    >> So you have the method
    >>
    >> public LinkedHashMap<String, Integer> getSortedMap() {
    >> //do stuff
    >> }
    >>
    >> (not necessarily public)
    >>
    >> Now the design principle says, the method signature should instead be
    >>
    >> public Map<String, Integer> getSortedMap() {
    >> //do stuff
    >> }
    >>
    >> The problem is, where I'm creating sortedMap above, I need the map to
    >> retain the insertion order. If what's returned actually is a Map,
    >> rather than a LinkedHashMap,

    >
    > (I'll assume that you meant "If what's returned is not an implementation
    > of LinkedHashMap...", rather than addressing the literal point.)
    >
    >> then the results the user actually sees
    >> are going to be in the wrong order. Making things worse, in this case,
    >> nothing would actually break, only the end user would notice anything
    >> was actually wrong.
    >>
    >> So in this case, it seems to me, that using LinkedHashMap in the
    >> method signature makes sense. The fact that the return retains the
    >> insertion order is an integral part of what the method does.

    >
    > I would say that it was still better just to use Map, as LinkedHashMap
    > need not be the only implementation with the predictable iteration that
    > the caller expects. Additional documentation is then required, of course.


    Good point. It turns out there's a LinkedMap in Apache Commons that
    also does this.

    > Hypothetically, what you might want is an intermediate interface between
    > Map and LinkedHashMap, e.g. InsertionOrderedMap, which extends Map but
    > adds no methods. This might provide some sort of compile-time
    > protection, that mere documentation cannot provide.
    >
    > However, IMHO, its value is dubious, for a few reasons:
    >
    > * An implementation of InsertionOrderedMap can be compiled without
    > meeting the insertion-order requirement.
    > * Another implementation of Map can be compiled without extending
    > InsertionOrderedMap while meeting the insertion-order requirement.
    > * Specifically for LinkedHashMap, it can actually provide access
    > order instead, so it couldn't implement both InsertionOrderedMap
    > and a similar AccessOrderedMap without breaking at least one
    > requirement, whichever way a particular instance was configured.
    >
    > OTOH, this sort of thing is already done, with Set and Collection. Set
    > extends Collection, but doesn't actually define any new methods. (It
    > does take advantage of 'overriding' the documentation, though.) Is it
    > worth having Set if:
    >
    > * you can implement Set without actually having set semantics?
    > * you can implement set semantics with just Collection?
    >
    > So, if you're prepared to trust that an implementation of Set or
    > InsertionOrderedMap follows the documented rules that can't be
    > compiler-enforced, perhaps it's as well do away with those interfaces,
    > and just put the documentation on methods like getSortedMap, and trust
    > that they are implemented accordingly.


    That may be the best overall approach. The Javadoc for java.util.Map
    explicitly notes that different implementations can provide different
    behavior here.

    --
    Jim Janney
    Jim Janney, May 5, 2011
    #3
  4. On 11-05-05 07:41 PM, Jim Janney wrote:
    > Steven Simpson <> writes:

    [ SNIP ]

    >> OTOH, this sort of thing is already done, with Set and Collection. Set
    >> extends Collection, but doesn't actually define any new methods. (It
    >> does take advantage of 'overriding' the documentation, though.) Is it
    >> worth having Set if:
    >>
    >> * you can implement Set without actually having set semantics?
    >> * you can implement set semantics with just Collection?
    >>
    >> So, if you're prepared to trust that an implementation of Set or
    >> InsertionOrderedMap follows the documented rules that can't be
    >> compiler-enforced, perhaps it's as well do away with those interfaces,
    >> and just put the documentation on methods like getSortedMap, and trust
    >> that they are implemented accordingly.

    >
    > That may be the best overall approach. The Javadoc for java.util.Map
    > explicitly notes that different implementations can provide different
    > behavior here.
    >

    Which is actually the case for a bunch of collections. Of relevance to
    this thread, LinkedHashMap provides 4 new method implementations, but 3
    of them are overrides of methods in concrete class HashMap. Those 3 in
    turn are overrides of method implementations in AbstractMap. The two
    instantiable classes have different behaviour, though, so if I decide
    that it's really LinkedHashMap that I need, another Map will not do. So
    why refer to it as a Map?

    With Java collections, one may pass around a Map or a Set or a List,
    with reasonable trust that whatever is being shuttled around has
    high-level map/set/bag semantics, but many/most Java programmers also
    care a fair bit about the actual implementations used, because it
    matters. They behave differently. They may all be Maps or Sets or Lists,
    but they're not the same. It frequently is not acceptable to pass around
    just any Map or Set or List, so why is "program to the interface" so
    highly touted here? Beats me.

    My 2 cents worth. And I'm being a bit of non-conformist and shit
    disturber here. :)

    And rather than rely only on documentation, what's wrong with using the
    language? I've picked ConcurrentHashMap, say, because I damned well need
    ConcurrentHashMap, and I want and require getAppropriateMap() to return
    a ConcurrentHashMap, so perhaps it is the proper thing to have getMap()
    return ConcurrentHashMap.

    Just a thought. I hearken back to your earlier observation, that:

    "The point of programming to the interface is to make it easier to
    substitute a different implementation, which implies that any reasonable
    implementation can be used. If this is not true, if the code that uses
    the object relies on behavior only found in one implementation, then
    there is no benefit to using the interface, and you make it more
    inviting for someone to break things later on."

    This is absolutely true. And the Java Collections API is rife with
    possibilities for where "programming to the interface" is undesirable
    and counterproductive.

    AHS
    Arved Sandstrom, May 6, 2011
    #4
  5. On 05/05/11 23:29, Tom Anderson wrote:
    > On Thu, 5 May 2011, Steven Simpson wrote:
    >> Hypothetically, what you might want is an intermediate interface
    >> between Map and LinkedHashMap, e.g. InsertionOrderedMap, which
    >> extends Map but adds no methods. This might provide some sort of
    >> compile-time protection, that mere documentation cannot provide.
    >>
    >> However, IMHO, its value is dubious, for a few reasons:
    >>
    >> * An implementation of InsertionOrderedMap can be compiled without
    >> meeting the insertion-order requirement.
    >> * Another implementation of Map can be compiled without extending
    >> InsertionOrderedMap while meeting the insertion-order requirement.

    >
    > These points are true of any interface! Look:
    >
    > public class DubiousComparator implements Comparator<String> {
    > public int compare(String a, String b) {
    > return 1;
    > }
    > }


    Yes, this has always bothered me about my argument. Perhaps I can
    refine it by noting that you could take any non-Set Collection
    implementation, extend it and declare it implementing Set without
    actually adding any extra methods. To make any class a Comparator, you
    will likely have to write an extra method, and deliberately write it
    wrongly.

    Oh, okay, it's not particularly robust (isn't upgrading a Collection
    implementation to a Set also deliberate?), but interfaces with no
    additional methods make me uneasy.

    > I feel Zapanaz's pain, but i think it's just One Of Those Things.


    Me too. Certainly, I would like to express these behavioural
    guarantees/constraints in a compiler-checkable way, but I don't think
    marker interfaces cut it. One reason is that behaviour may be
    instance-specific rather than class-specific (as seen with
    LinkedHashMap). Another is that guarantees may be dropped in subclasses
    (as WeakHashMap/IdentityHashMap do regarding Object.equals - is that
    wrong?). Yet another is that behaviours might be combined orthogonally,
    e.g. InsertionOrdered, NullKeys, NullValues, and you'd need to create an
    interface combining just those three - while using LinkedHashMap as the
    return type might offer more behaviours than you wish to promise. I'm
    resigned to leaving these issues as a matter of documentation.
    Steven Simpson, May 6, 2011
    #5
  6. Steven Simpson

    Lew Guest

    Steven Simpson wrote:
    > Me too. Certainly, I would like to express these behavioural
    > guarantees/constraints in a compiler-checkable way, but I don't think
    > marker interfaces cut it. One reason is that behaviour may be
    > instance-specific rather than class-specific (as seen with
    > LinkedHashMap). Another is that guarantees may be dropped in subclasses
    > (as WeakHashMap/IdentityHashMap do regarding Object.equals - is that
    > wrong?). Yet another is that behaviours might be combined orthogonally,
    > e.g. InsertionOrdered, NullKeys, NullValues, and you'd need to create an
    > interface combining just those three - while using LinkedHashMap as the
    > return type might offer more behaviours than you wish to promise. I'm
    > resigned to leaving these issues as a matter of documentation.


    This sounds like a use case for annotations.

    --
    Lew
    Honi soit qui mal y pense.
    http://upload.wikimedia.org/wikipedia/commons/c/cf/Friz.jpg
    Lew, May 6, 2011
    #6
    1. Advertising

Want to reply to this thread or ask your own question?

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. Jim Janney
    Replies:
    43
    Views:
    1,030
    Arved Sandstrom
    May 15, 2011
  2. Roedy Green
    Replies:
    1
    Views:
    315
    Jim Janney
    May 6, 2011
  3. Michal Kleczek
    Replies:
    10
    Views:
    527
    Arved Sandstrom
    May 10, 2011
  4. Pallav singh
    Replies:
    0
    Views:
    354
    Pallav singh
    Jan 22, 2012
  5. Pallav singh
    Replies:
    0
    Views:
    399
    Pallav singh
    Jan 22, 2012
Loading...

Share This Page