ClarityOfTypes

From APIDesign

(Difference between revisions)
Jump to: navigation, search
(Clarity for Implementors)
Line 34: Line 34:
=== [[Clarity]] for [[ProviderAPI|Implementors]] ===
=== [[Clarity]] for [[ProviderAPI|Implementors]] ===
-
The use case for [[ProviderAPI|implementors]] is slightly changed, but not dramatically. Instead of subclassing ''Mixed'' class they need to implement the ''Provider'' interface and hold a reference to the ''Callback'' class (in case they wish to call its methods, otherwise this can be left out). Instead of creating the instance of ''NonMixed'' subclass directly, they need to use the ''create'' [[factory]] method.
+
The use case for [[ProviderAPI|implementors]] is slightly changed, but not dramatically. Instead of subclassing ''Mixed'' class they need to implement the ''Provider'' interface and hold a reference to the ''Callback'' class (in case they wish to call its methods, otherwise this can be left out). Instead of creating the instance of ''NonMixed'' subclass directly, they need to use the ''create'' [[factory]] method:
 +
 
 +
<source lang="java" snippet="sidemeanings.Mixed.Clean.Use"/>
By using this [[DelegationAndComposition]] they get some benefits: it is clear which methods they have to implement (all in the ''Provider'') interface. They can use multiple inheritance, which works with interfaces but not class in [[Java]].
By using this [[DelegationAndComposition]] they get some benefits: it is clear which methods they have to implement (all in the ''Provider'') interface. They can use multiple inheritance, which works with interfaces but not class in [[Java]].

Revision as of 16:22, 1 April 2009

The fuzziness is all around us, including our APIs. The essay ClarityOfAccessModifiers explains the importance of correct choice of access modifiers. The EliminateFuzzyModifiers gives a recipe how to turn those fuzzy meanings into clear ones. Yet the result is still not ideal. Something is missing.

This essay en-light what is it. More on the topic can be found in Chapter 10 of TheAPIBook. Here is a shorter version.

The result of elimination of public, protected and public abstract can be triplication of each fuzzy method. This may not be bad in all times, for example CharsetDecoder successfully provides duplicated versions of its methods (one for ClientAPI users and one for providers subclassing the class), but on the other hand this is definitely distracting. Here is an example of correct, but complex elimination:

Code from Arithmetica.java:
See the whole file.

public final int sumTwo(int one, int second) {
    return overridableSumTwo(one, second);
}
protected abstract int overridableSumTwo(int one, int second);
protected final int defaultSumTwo(int one, int second) {
    return one + second;
}
 

Callers to the API reading Javadoc see all the methods and in fact only one third of them is of some interest. They need to ignore the rest. It would be much better to separate the concerns and have one API for callers and one for implementors.

This can be achieved by using DelegationAndComposition. Create one type for client API users, another type for implementors and yet another to hold methods used for callback from providers to the superclass implementation (callback means protected final as mentioned here).

Contents

Theory

Imagine we have a class full of clear, single meaning access modifiers. Just like the following Mixed one:

Code from MixedClass.java:
See the whole file.

public abstract class MixedClass {
    private int counter;
    private int sum;
 
    protected MixedClass() {
        super();
    }
 
    public final int apiForClients() {
        int subclass = toBeImplementedBySubclass();
        sum += subclass;
        return sum / counter;
    }
 
    protected abstract int toBeImplementedBySubclass();
 
    protected final void toBeCalledBySubclass() {
        counter++;
    }
}
 

However, we are not happy with mixing API method for different users in a single class. Instead we want to separate them into individual types each targeted to appropriate audience. One possible rewrite using DelegationAndComposition is to introduce one class for ClientAPI users, one interface for subclassers:

Code from NonMixed.java:
See the whole file.

public final class NonMixed {
    int counter;
    private int sum;
    private final Provider impl;
 
    NonMixed(Provider impl) {
        this.impl = impl;
    }
 
    public final int apiForClients() {
        int subclass = impl.toBeImplementedBySubclass();
        sum += subclass;
        return sum / counter;
    }
}
 

The ClientAPI class NonMixed is final and as such all its methods are public final which is single meaning access modifier.

The originally protected abstract methods are moved to Provider interface. Interface methods are public and this might seem like a contradiction to ClarityOfAccessModifiers. However, it is not. The instances of Provider interface are not accessible to clients of the API. They are hidden and only useful for the create factory method that turns them into NonMixed instances. As such they are single meaning methods with only one message: implement me, don't call me!. Their clarity is kept.

There is also the Callback class which holds originally protected final methods. It is a final class, with non-public constructor, so its creation is fully under the control of the NonMixed API infrastructure. Clients of the NonMixed class cannot get access to it - the reference is only available to the Provider interface via the initialize method. As such its use is protected, only Provider interface implementor can call its methods. Moreover the class is final and as such its methods have only single meaning: call me, if you are implementing the provider. Fully clear again.

Clarity for Clients

Clients that hold reference to NonMixed class see only its public methods in Javadoc, IDE code completion, etc. They are not distracted by details useful only for providers (except the create factory method, but that one could be moved to other class, it does not have to be in NonMixed one as in this example). The code that callers need to provide is completely the same as in the original Mixed class - e.g. we achieved clarity for callers without complicating their code even a bit!

Clarity for Implementors

The use case for implementors is slightly changed, but not dramatically. Instead of subclassing Mixed class they need to implement the Provider interface and hold a reference to the Callback class (in case they wish to call its methods, otherwise this can be left out). Instead of creating the instance of NonMixed subclass directly, they need to use the create factory method:

Code from MixedTest.java:
See the whole file.

@Test public void useWithoutMixedMeanings() {
    class AddFiveMixedCounter implements NonMixedFactory.Provider {
        private Callback callback;
 
        public int toBeImplementedBySubclass() {
            callback.toBeCalledBySubclass();
            return 5;
        }
 
        public void initialize(Callback c) {
            callback = c;
        }
    }
    NonMixed add5 = NonMixedFactory.create(new AddFiveMixedCounter());
    assertEquals("5/1 = 5", 5, add5.apiForClients());
    assertEquals("10/2 = 5", 5, add5.apiForClients());
    assertEquals("15/3 = 5", 5, add5.apiForClients());
}
 

By using this DelegationAndComposition they get some benefits: it is clear which methods they have to implement (all in the Provider) interface. They can use multiple inheritance, which works with interfaces but not class in Java.

Evolution Aspects

Using this kind of DelegationAndComposition is useful from evolution point of view. It is easy to evolve the client part of the API, as adding new methods into final class is absolutely backward compatible in Java.

It is easy to evolve the Callback contract as well. It is also final and as such adding new methods there is OK. And new methods in Callback class is exactly what the providers of the Provider interface may need in future.

Also the evolution of Provider interface is possible and easy. Adding new methods into subclassable types is of course not backward compatible at all, but one can always create new interfaces like ExtraProvider. Because of DelegationAndComposition this addition has no effect on ClientAPI - the NonMixed class does not need to change at all. The only thing that may need to be added is new factory method for each new interface. But that is quite well thought evolution plan.

<comments/>

Personal tools
buy