I am developing a small library in Java that can read an image from a compressed image data source (eg a PNG file) and decode it, returning an Image
object. The Image
class has various functions named Image.createImage(...)
that can create an image from the specified argument.
I have already added several public Image.createImage(...)
methods to the library API, one for each data source type: Image.createImage(Path)
, Image.createImage(InputStream)
, Image.createImage(String)
, Image.createImage(byte[])
, ..., to let the user easily get an Image
from various data sources without having to type a lot of code.
However this means the Javadoc is duplicated for each method and the API gets larger, so a bit harder to learn, even though the helper methods themselves are quite trivial (eg Image.createImage(byte[])
is simply return Image.createImage(new ByteArrayInputStream(array));
), so I've decided to redesign my library API design.
I have thought of three different ways to redesign my Image
API:
Image.createImage(InputStream)
, and let the user create an input stream from his data source himself (and perhaps including some example code in the Javadoc, eg "to decode an image stored in a file, you may use Files.newInputStream(Paths.get(...))
...") Image.createImage(ByteBuffer)
, Image.createImage(Path)
, ... with the Javadoc essentialy duplicated for each method (this is actually the current situation) DataSource
class that can be constructed from one of the data source types (eg new DataSource(Path)
, ...), and have a single image decoding method that takes a DataSource
object, eg Image.createImage(DataSource)
I was wondering which design is the best one.
InputStream
from a Path or a resource String. I'm tempted to choose this design. Image
if he has to read the Javadoc for both Image
and DataSource
, then find how to create a DataSource
, and I also personally find it very "heavyweight"-ish (reminds me of very large frameworks with a huge number of classes and packages, whereas I like to keep my projects small and concise). If you were to use this library, which design would you prefer? Is the first design the best?
According to Joshua Bloch,
API Should Be As Small As Possible But No Smaller
• When in doubt leave it out ─ Functionality, classes, methods, parameters, etc. ─ You can always add, but you can never remove
Take a look at page 14 from this link
http://www.cs.bc.edu/~muller/teaching/cs102/s06/lib/pdf/api-design
You actual design seems really fine.
Provide a (helper) method for each data source type, eg Image.createImage(ByteBuffer), Image.createImage(Path), ... with the Javadoc essentialy duplicated for each method (this is actually the current situation)
More an API is simple to use, better it is.
Providing method overloading is often an advantage as the client doesn't need to remember many things to apply a processing/create an object with a different flavor.
He/she has to remember only of the
method.
Look at the JDK classes in the java.io
packages : FileInputStream
, FileOutputStream
.
These are designed in this way :
public FileInputStream(String name) throws FileNotFoundException {
public FileInputStream(File file) throws FileNotFoundException {
public FileInputStream(FileDescriptor fdObj) {
Of course, if you define many overloaded methods, for example 8 or more, things are different.
Your API may become harder to read, use and maybe error prone.
it doesn't seem the case for you.
Having javadoc to document for each method should not be a problem.
The client has not to learn them by heart.
Besides, javadoc of them should be very similar :
Image.createImage(Path)
Image.createImage(InputStream)
, Image.createImage(String)
, - Image.createImage(byte[])
And the javadoc of these methods may always refer to a common method with the @see
or {@link}
javadoc annotation.
Finally, as each method uses a distinct type as parameter, we can easily guess the variance between.
You already got good answers, but regarding javadoc: simply make use of linking to other methods, using:
{@link package.class#member label}
Meaning: if you really have a set of related methods that do "the same" require different parameters - then it is good practice to not only avoid code duplication, but to also avoid documentation duplication. So:
/**
* This is the extensively documented "anchor" method ...
* @param String path to image as String ...
*/
public void createImage(String path) { ...
/**
* See {@link ...
* and some additional information ...
From a design point of view, it really depends on what is your aim with the library and how far you want to expand it.
First option creates a really lightweight library easy to use in multiple occasion, but leaves to the user a lot of the work you know it is needed to be done, plus some you don't really know. It is easy to write and doesn't limit the way you can use it but it is not so easy to use and it might really not be worth it if you just want to factor code you use a lot
If you keep it as it is, you will keep a huge (from the outside) library extremely easy to use. But, as other mentioned, might be difficult to mantain.
Third case is the more object-oriented in my opinion. You abstract the idea of a DataSource
and you can basically create a hierarchy of classes, one for each type of source you need. Easy to extend, easy to use (with proper factory methods). To create a mantainable library I would use this approach.
On the other side, if your only concern is the repetition of the JavaDoc description, you can always use a {@link} tag to write it once and refer it everywhere else
The problem appears to be related to creation . Consider an ImageFactory
which supports the multiple data sources. The glue code is in the factory so there's only a single Image
constructor.
Perhaps make the constructor of Image
private so users will be required to use the factory.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.