Separate APIs for Clients and Providers

From APIDesign

Revision as of 03:14, 12 September 2009 by JaroslavTulach (Talk | contribs)
(diff) ←Older revision | Current revision (diff) | Newer revision→ (diff)
Jump to: navigation, search

Contents

Have You Ever Wondered...?

Have you ever subclassed some API class and started to ask yourself: "Shall I override its particular method or just call it?" Or: "Is this method public because external users shall call it or am I supposed to override it?". Chapter 8 brings you answer to such common worries. It analyses the differences between evolution of APIs targeted to different clients, analyses what kind of messages various Java access modifiers carry and gives a recipe to convert a class full of methods with unclear multiple meanings into an API that carries just one, clear message for every developer using it.

The Writer

Significant part of this chapter builds a showcase around the Writer example. Do you know that in JDK 1.5 new methods has been added into the Writer class to work with CharSequences? Yes, it was. Could it be left abstract? No, that would not be compatible! It needed some implementation. Some, but which one? There seems to be multiple implementation choices to use. Now a small quiz: "which one would you choose?"

Code from AltBufferedWriter.java:
See the whole file.

public Writer append(CharSequence csq) throws IOException {
    /* thrown an exception as this method is new and 
     subclasses need to override it */
    throw new UnsupportedOperationException();
}
 

Possible, but useless, for clients calling the Writer.append method. They would always have to code very defensively, as shown here:

Code from BufferedWriterThrowingExceptionTest.java:
See the whole file.

try {
    bufferedWriter.append(what);
} catch (UnsupportedOperationException ex) {
    bufferedWriter.write(what.toString());
}
 

Of course, this is a very horrible API. You want to guarantee to clients calling into Writer that reasonable implementation of the append method is provided:

Code from AltBufferedWriter.java:
See the whole file.

if (csq == null) {
    write("null");
} else {
    write(csq.toString());
}
return this;
 

This looks good, as clients can always rely on the append method to be implemented. Even by writers written against JDK 1.4 version of the Writer. This is not bad, however it is also not very efficient, as it always converts the whole CharSequence to String. In case of big sequences, like onces representing whole CD, this is a way to ask for OutOfMemoryErrors:

Code from BufferedWriterOnCDImageTest.java:
See the whole file.

CountingWriter writer = new CountingWriter();
CDSequence cdImage = new CDSequence();
BufferedWriter bufferedWriter = new BufferedWriter(writer);
bufferedWriter.append(cdImage);
assertEquals(
    "Correct number of writes delegated", 
    cdImage.length(), writer.getCharacterCount()
);
 

There must be more effective way to do this! Yes, it is. Please read the chapter 8 of TheAPIBook to learn what to do and how to prevent situations like this.

Errata

Page 132

Implementation of Playback interface should be named MyPlaybackPrints instead of MyCallbackPrints.

Page 134

Yoshiki suggests to use is not well understood instead of not part of conventional wisdom as that is much clear to international speaker in the paragraph that talks why client/provider API separation is much more visible in C and very blur in Java:

In the case of C, the amount of work to produce an SPI, such as a callback, is enough to prevent a beginner from even trying it. Your knowledge has to grow significantly to attain a state where you can or need to design an SPI. However, in Java any declared method that is not private, final, or static is an invitation for someone to provide a callback and thus create an accidental SPI. Often programmers and teachers don’t clearly understand this. It’s not part of conventional wisdom (e.g. not well understood). Although Java books introduce public, nonstatic, and nonfinal methods in their first chapters, or at least as soon as they start talking about applets, they don’t provide proper warnings about all their consequences. Though that might be fine for simple development, when you start to design APIs, all the bad habits learned at the start come back to haunt you in the form of mistakes.

However the message remains the same: Make sure you separate client and provider APIs and clarify meaning of types in your API.

Page 134

There should be append method mentioned (instead of write) in this sentence: "This is reasonable behavior for implementors because those who subclassed Writer have not implemented the new write method."

Page 140

While introducing code for separating API/SPI concerns, after saying "Say the original version of Writer looked like the following:", final Writer's code is shown with one of its methods being "create(java.io.Writer)", which implies java.io.Writer's pre-existence. I am guessing that that method is there for convenience, because otherwise all java.io.Writer's method bodies should have been copied over into an Impl implementation in order to be able to run the sample source code, but for clarity, that method should probably not appear in that paragraph (maybe just in the downloadable source code with comments in its header).

True, the "create(java.io.Writer)" method is not really necessary. On the other hand it provides real world example of the API use, matching it to some API familiar to the user. It demonstrates that the new Writer is good enough replacement for the standard java.io.Writer. It shows later that by evolving the SPI to understand CharSequences the delegation to java.io.Writer can be done much more easily.

--JaroslavTulach 18:45, 31 July 2009 (UTC)

Personal tools
buy