'. '

DefaultMethods

From APIDesign

(Difference between revisions)
Jump to: navigation, search
(Useful?)
(Can you disagree?)
(2 intermediate revisions not shown.)
Line 78: Line 78:
As such [[I]] am suggesting to '''not''' use [[default methods]] when designing [[API]]. For example when designing a [[visitor]], it is better to get along without them. A [[good]] [[API]] designed with [[cluelessness]] in mind, shall avoid [[default methods]]!
As such [[I]] am suggesting to '''not''' use [[default methods]] when designing [[API]]. For example when designing a [[visitor]], it is better to get along without them. A [[good]] [[API]] designed with [[cluelessness]] in mind, shall avoid [[default methods]]!
 +
 +
== Can you disagree? ==
 +
 +
[[I]] also believe [[default methods]] can be useful, so [[I]] fully understand if you are hesitating to accept the above arguments. However the conclusions are inevitable from the following chain of thoughts:
 +
 +
* Do you agree that mixing [[APIvsSPI|client and provider APIs]] is causing troubles?
 +
* Do you agree that [[clarity]] is an important aspect of an [[API]]?
 +
* Are you willing to search for [[ClarityOfTypes]] in the [[API]] that you design?
 +
 +
Then it is [[clarity|clear]]: Use '''final''' classes to represent [[ClientAPI]] types and use ''pure'' '''interface''' to represent [[ProviderAPI]]. There is no place for [[default methods]] in this ideal setup... but of course, we are humans, so we always find excuses for using/doing what we like, right?
[[Category:APIDesignPatterns:Anti]] [[Category:APIDesignPatterns:Clarity]] [[Category:APIDesignPatterns:Evolution]]
[[Category:APIDesignPatterns:Anti]] [[Category:APIDesignPatterns:Clarity]] [[Category:APIDesignPatterns:Evolution]]

Revision as of 08:48, 6 March 2018

DefaultMethods is a new feature of JDK8. It allows one to specify (default) implementation of Java interface method.

Adding methods with some implementation breaks the clear separation between Java interface (used to specify a contract only) and class (provides some implementation). Many members of the Java community were crying for having a way to add methods into already published interface in a backward compatible way for ages. Of course, as usual in Java, only when the JDK team felt the need itself (because of adding a lot of new Lambda methods into Collection & co. classes), it listened to the general request.

On the other hand, there were people claiming that DefaultMethods are bad - that an interface should be a code-less specification and the change would have consequences. And yes, it does. This page describes some of them.

Contents

Useful?

From one point of view, it is a useful concept. Especially if one paints himself into a corner by not following advices of TheAPIBook when designing the first version of the API. If one mixes client and provider API into one type - like List, then one gets into trouble. On one side people call into the type (e.g. use it as ClientAPI). On the other hand, there are people who implement it - e.g. use the List type as a ProviderAPI. They create standard classes like ArrayList, but also numerous custom implementations written by developers around the globe.

Later an evolution of the [[API] may require one to add new methods into the ClientAPI side of the contract. In such situation one is going to welcome default methods as a nice way to get out of the corner. By adding default methods one can enhance the ClientAPI part of the interface, while keeping (most of) the compatibility for those who implement the interface.

However the concept of default methods also comes with drawbacks.

By-Passing Your Interface

When I was implementing netbeans:Html4Java API, I had to create an observable list - so I did it and created JSONList. I've carefully overwritten each public method that modified the list state and called a change notification handler. What could go wrong?

@Override
public boolean add(T e) {
  boolean ret = super.add(e);
  notifyChange();
  return ret;
}

I thought I did everything correctly, as all the methods of the List interface were properly overwritten. But (as you can probably guess) a problem appeared with DefaultMethods.

The Sorting Problem

If you write this code:

People p = new People();
List<String> names = p.getNicknames(); // returns the JSONList implementation
Collections.sort(names);

it works properly on JDK7, but it gets broken on JDK8. The code needs to compile on JDK7, so no DefaultMethods (introduced in JDK8) are called. In spite of that the code behaves differently on JDK8 and the notification change isn't delivered!

The problem is that the static sort method in Collections does the sorting by itself in JDK7, but in JDK8 it delegates to List.sort:

public static <T extends Comparable<? super T>> void sort(List<T> list) {
    list.sort(null);
}

In addition to that there is an optimized implementation of sort in ArrayList that bypasses all existing (at time of JDK7) methods and sorts directly the internal array:

@Override
@SuppressWarnings("unchecked")
public void sort(Comparator<? super E> c) {
    final int expectedModCount = modCount;
    Arrays.sort((E[]) elementData, 0, size, c);
    if (modCount != expectedModCount) {
        throw new ConcurrentModificationException();
    }
    modCount++;
}

as such the JSONList can be sorted without notifying about changes. None of the methods known in JDK7 is called and yet the content of the array is altered. This is a similar problem as the one with delegation and adding new methods into existing types as discussed in Chapter 8 of TheAPIBook.

The solution in this case is to overwrite the sort method, but this is the kind of problems we can expect with DefaultMethods - interface no longer represents a snapshot of a protocol at a time, it evolves (which is what we wanted), but also with all the (sometimes unwanted) consequences.

Increasing Fuzziness

The problem with default methods is that they increase fuzziness. interface is no longer pure it used to be. Java interface with default methods is fuzzier than pure interface (without any implementation). interface with some (default) implementation no longer clearly defines a contract - e.g. its use for ProviderAPI is no longer as sharp as it could be.

When somebody implements such interface, one can choose to implement the default methods or ignore them. That increases fuzziness. Moreover when we look at the concept from the evolution point of view, it may get even fuzzier.

Envision a pure interface defined in an initial version. Imagine the interface being implemented by various 3rd party developers. Later the interface gets enriched with few default methods. When you look at an implementation of the interface which doesn't override the default methods: What can you conclude?

Either they don't implement the default methods as they weren't existing when the implementations were written - e.g. they compiled against original version of the API. Or they don't implement the default methods because they are happy with their default implementation. That may make (and does make as the Collections.sort example shows) a difference! But it is impossible to separate these two cases apart!

As such I am suggesting to not use default methods when designing API. For example when designing a visitor, it is better to get along without them. A good API designed with cluelessness in mind, shall avoid default methods!

Can you disagree?

I also believe default methods can be useful, so I fully understand if you are hesitating to accept the above arguments. However the conclusions are inevitable from the following chain of thoughts:

Then it is clear: Use final classes to represent ClientAPI types and use pure interface to represent ProviderAPI. There is no place for default methods in this ideal setup... but of course, we are humans, so we always find excuses for using/doing what we like, right?

Personal tools
buy