ImplementOnlyInterface
From APIDesign
m (Correct typo: know => known) |
|||
(7 intermediate revisions not shown.) | |||
Line 1: | Line 1: | ||
- | By exposing a [[Java]] '''interface''' in an [[API]] one usually creates two sides of an | + | By exposing a [[Java]] '''interface''' in an [[API]] one usually creates [[3SidesToEveryAPI|two sides of an API]]. One for [[ClientAPI|clients]] that may call methods on instances assignable to that interface and one for [[ProviderAPI|providers]] that may implement that interface. This is often not really desirable. Each of these [[API]] types has different [[evolution]] characteristics. Also the [[clarity]] of such interface methods may not be as clear as it could. More about this in [[ClarityOfAccessModifiers]] and [[ClarityOfTypes]]. |
Most common example of such ''interface'' with double purpose is ''java.lang.Runnable''. Everyone implements it, there is a lot of methods in [[Java]] standard libraries that also accept it. Another group of such interfaces includes almost all [[JavaBean]]s listeners. Implementing ''java.beans.PropertyChangeListener'' is example [[ProviderAPI]]. Almost everyone wrote such code at least once. Also, as [[JavaBean]]s specification is common, a lot of programmers also used the interface in [[ClientAPI|client]] way after adding following code into their classes: | Most common example of such ''interface'' with double purpose is ''java.lang.Runnable''. Everyone implements it, there is a lot of methods in [[Java]] standard libraries that also accept it. Another group of such interfaces includes almost all [[JavaBean]]s listeners. Implementing ''java.beans.PropertyChangeListener'' is example [[ProviderAPI]]. Almost everyone wrote such code at least once. Also, as [[JavaBean]]s specification is common, a lot of programmers also used the interface in [[ClientAPI|client]] way after adding following code into their classes: | ||
Line 10: | Line 10: | ||
</source> | </source> | ||
- | Interfaces contain '''public''' methods which are | + | Interfaces contain '''public''' methods which are known to have [[ClarityOfAccessModifiers|multiple meanings]] as such they are often subject to double use. One can prevent that by usage of [[ImplementOnlyAbstractClass]] pattern, yet it may not be always the preferred solution. |
=== Implement Only Interface === | === Implement Only Interface === | ||
Line 30: | Line 30: | ||
There is no way some other module using [[API]] could get access to instance of such ''Provider'' implementation. As such the ''Provider'' methods has just a single meaning: ''implement me!''. | There is no way some other module using [[API]] could get access to instance of such ''Provider'' implementation. As such the ''Provider'' methods has just a single meaning: ''implement me!''. | ||
- | === | + | === Self Usage === |
One might argue, that the same module (e.g. compilation and runtime unit) can start to access its own implementation: | One might argue, that the same module (e.g. compilation and runtime unit) can start to access its own implementation: | ||
Line 47: | Line 47: | ||
=== Reexporting API === | === Reexporting API === | ||
- | Possible problem may arise when the ''MyProvider'' class is part of [[API]] of its module. Then other modules can use it and indeed, can also call its methods. Then the clarity of the ''Provider'' methods lost. However this is not fault of the original ''NonMixedFactory'' [[API]], but fault of this new one which re-exports the interface. | + | Possible problem may arise when the ''MyProvider'' class is part of [[API]] of its module. Then other modules can use it and indeed, can also call its methods. Then the clarity of the ''Provider'' methods is lost. However this is not fault of the original ''NonMixedFactory'' [[API]], but fault of this new one which re-exports the interface. |
- | The original [[API]] has still its [[clarity]], but the whole system's single meaning is compromised by additional [[API]]s. There | + | The original [[API]] has still its [[clarity]], but the whole system's single meaning is compromised by additional [[API]]s. There are just tho things the author of the original [[API]] can do about this: |
+ | * hope others won't do such silly things during re-export | ||
+ | * use [[ImplementOnlyAbstractClass]] pattern | ||
+ | I personally prefer to just hope. So far it work fine, I have not found a module that would improperly re-export my [[API]] interfaces. | ||
=== By Passing === | === By Passing === | ||
Line 61: | Line 64: | ||
</source> | </source> | ||
- | Then other modules can get access to such JPanel through AWT component hierarchy and use '''instanceof''' check to safely get foreign ''Provider'' implementation which can then be ''called''. | + | Then other modules can get access to such JPanel through AWT component hierarchy and use '''instanceof''' check and cast to safely get foreign ''Provider'' implementation which can then be ''called''. |
- | However | + | However the fact that there is some ''JPanel'' that can be cast to ''Provider'' is an example of another [[APITypes|API type]] - a kind of runtime API. This means the module providing ''MyPanel'' is also providing additional [[API]] (an information that there can be a ''JPanel'' that also implements ''Provider'') and we are in situation similar to one analysed in ''Reexporting API'' section. |
Current revision
By exposing a Java interface in an API one usually creates two sides of an API. One for clients that may call methods on instances assignable to that interface and one for providers that may implement that interface. This is often not really desirable. Each of these API types has different evolution characteristics. Also the clarity of such interface methods may not be as clear as it could. More about this in ClarityOfAccessModifiers and ClarityOfTypes.
Most common example of such interface with double purpose is java.lang.Runnable. Everyone implements it, there is a lot of methods in Java standard libraries that also accept it. Another group of such interfaces includes almost all JavaBeans listeners. Implementing java.beans.PropertyChangeListener is example ProviderAPI. Almost everyone wrote such code at least once. Also, as JavaBeans specification is common, a lot of programmers also used the interface in client way after adding following code into their classes:
public void addPropertyChangeListener(PropertyChangeListener l) { this.listener = l; } public void removePropertyChangeListener(PropertyChangeListener l) { this.listener = null; } protected final void fireChange() { this.listener.firePropertyChange(new PropertyChangeEvent(this, null, null, null)); }
Interfaces contain public methods which are known to have multiple meanings as such they are often subject to double use. One can prevent that by usage of ImplementOnlyAbstractClass pattern, yet it may not be always the preferred solution.
Contents |
Implement Only Interface
On the other hand, the algorithm to redesign multi meaning classes into types with clear meanings suggests to use create interface Provider and claims that such interface methods have just a single meaning. Can interface have just a single meaning? parren questioned that:
The Provider interface is a public interface residing within a public class. I can implement this interface and throw it around at will. For that reason, instances of the interface are accessible to clients of the API. For the same reason, they say to me: implement me and do whatever you want with me, including call me.
Sort of true, but I still insist that the interface methods have only single meaning within its API. Imagine there is thousands users of following API:
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++; } } }
These usages are packaged in their own modules (independent compilation units) and they do not know about each other. They only compile against and use the above API. Each of them can create a class that implements the Provider interface. However the only useful thing that can be done with such class, is to create its instance and pass it into the NonMixedFactory.create method. But that method keeps the instance for private purposes and does not let any other user of the API to access it.
There is no way some other module using API could get access to instance of such Provider implementation. As such the Provider methods has just a single meaning: implement me!.
Self Usage
One might argue, that the same module (e.g. compilation and runtime unit) can start to access its own implementation:
class MyProvider implements Provider { ... } Provider p = new MyProvider(); p.toBeImplementedBySubclass();
True, this is possible use. More misuse than a use however. One could call the MyProvider methods directly by declaring the variable to be of MyProvider type. So the usage of Provider in this example is unnecessary.
However even with Provider variable, the meaning of Provider in the NonMixedFactory API is still clear. That fact that one module decides to internally misuse it can't affect that.
Reexporting API
Possible problem may arise when the MyProvider class is part of API of its module. Then other modules can use it and indeed, can also call its methods. Then the clarity of the Provider methods is lost. However this is not fault of the original NonMixedFactory API, but fault of this new one which re-exports the interface.
The original API has still its clarity, but the whole system's single meaning is compromised by additional APIs. There are just tho things the author of the original API can do about this:
- hope others won't do such silly things during re-export
- use ImplementOnlyAbstractClass pattern
I personally prefer to just hope. So far it work fine, I have not found a module that would improperly re-export my API interfaces.
By Passing
Last way for the Provider implementation to get into hands of other modules is escaping through some other API. For example one could:
class MyPanel extends JPanel implements NonMixedFactory.Provider { ... }
Then other modules can get access to such JPanel through AWT component hierarchy and use instanceof check and cast to safely get foreign Provider implementation which can then be called.
However the fact that there is some JPanel that can be cast to Provider is an example of another API type - a kind of runtime API. This means the module providing MyPanel is also providing additional API (an information that there can be a JPanel that also implements Provider) and we are in situation similar to one analysed in Reexporting API section.