ClarityOfTypes
From APIDesign
(→Clarity for Implementors) |
(→Theory) |
||
Line 18: | Line 18: | ||
<source lang="java" snippet="sidemeanings.Mixed.Dirty"/> | <source lang="java" snippet="sidemeanings.Mixed.Dirty"/> | ||
- | 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 | + | 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: |
<source lang="java" snippet="sidemeanings.Mixed.Clean"/> | <source lang="java" snippet="sidemeanings.Mixed.Clean"/> | ||
- | The [[ClientAPI]] class ''NonMixed'' is '''final''' and as such all its methods are '''public final''' which is [[ClarityOfAccessModifiers|single meaning access modifier]]. | + | The [[ClientAPI]] class ''NonMixed'' is '''final''' and as such all its methods are '''public final''' which is [[ClarityOfAccessModifiers|single meaning access modifier]]. Moreover there needs to be an [[API]] for ''subclassers'': |
+ | |||
+ | <source lang="java" snippet="sidemeanings.Mixed.Clean.Factory"/> | ||
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 [[ClientAPI|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. | 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 [[ClientAPI|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. | ||
Line 30: | Line 32: | ||
=== [[Clarity]] for [[ClientAPI|Clients]] === | === [[Clarity]] for [[ClientAPI|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 | + | 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. The code that [[ClientAPI|callers]] need to provide is completely the same as in the original ''Mixed'' class - e.g. we achieved [[clarity]] for [[ClientAPI|callers]] without complicating their code even a bit (see the calls in the example in following section)! |
=== [[Clarity]] for [[ProviderAPI|Implementors]] === | === [[Clarity]] for [[ProviderAPI|Implementors]] === | ||
Line 38: | Line 40: | ||
<source lang="java" snippet="sidemeanings.Mixed.Clean.Use"/> | <source lang="java" snippet="sidemeanings.Mixed.Clean.Use"/> | ||
- | By using this [[DelegationAndComposition]] they get | + | By using this [[DelegationAndComposition]] they get few 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]] and moreover they are well set for future, as [[evolution]] story of this pattern is quite good. |
=== Evolution Aspects === | === Evolution Aspects === |
Revision as of 16:27, 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:
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. Moreover there needs to be an API for subclassers:
Code from NonMixedFactory.java:
See the whole file.public final class NonMixedFactory { private NonMixedFactory() { } public static NonMixed create(Provider impl) { NonMixed api = new NonMixed(impl); Callback callback = new Callback(api); impl.initialize(callback); return api; } public interface Provider { public void initialize(Callback c); public int toBeImplementedBySubclass(); } public static final class Callback { final NonMixed api; Callback(NonMixed api) { this.api = api; } public final void toBeCalledBySubclass() { api.counter++; } } }
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. 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 (see the calls in the example in following section)!
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 few 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 and moreover they are well set for future, as evolution story of this pattern is quite good.
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/>