Friday, October 31, 2008

In search of the perfect API (part 2)

This is the second installment of a sort series about the evolution of the API of DesignGridLayout library.
Although DesignGridLayout is used as an example, this series is more general and its goal is to show how you can improve the API of any library, and what pitfalls you should avoid.

In the first installment of this short series, we have visited the initial API of DesignGridLayout (version 0.1) and seen several drawbacks it exhibited.

Release 0.9 (heavy refactoring of both API and implementation)

When I took over the DesignGridLayout project, a few months ago, I had decided to completely refactor the library source code and make the API has good as possible; in particular, I wanted to remove all drawbacks of version 0.1.

First of all, I have added interfaces in order to isolate API from the implementation details (too many public classes and methods were exposed in version 0.1).

Since, in DesignGridLayout, the accessible features are different if you use:
  • a grid row
  • a non-grid row (center, left or right)
  • an empty row (which sole purpose is to provide some empty vertical space)
I have first decided to make different methods in DesignGridLayout class to create these various kinds of rows; one of my goals was to be able to provide a specific API according to each kind of row; in addition, deciding the type of the row at the time you create it from DesignGridLayout would remove another problem of V0.1 related to multiple changes of the row type (which for the end user is non-sense).
public class DesignGridLayout {
...
public INonGridRow leftRow();
public INonGridRow rightRow();
public INonGridRow centerRow();
public IGridRow row();
public void emptyRow(int height);
}
All names starting with I are interfaces (I borrowed this convention from .NET, although arguable at the beginning, I found it quite interesting in my situation).

Here are the interfaces (simplified, I put only the main methods for clarity):
public interface INonGridRow {
public INonGridRow add(JComponent... children);
}

public interface IGridRow {
public IGridRow label(JLabel label);
public IGridRow add(JComponent... children);
public IGridRow add(JComponent child, int span);
public IGridRow empty();
public IGridRow empty(int span);
}
Now we clearly set the list of methods available to each kind of row, excluding any unsupported method (which we didn't do in V0.1).

We have also replaced the constant EMPTY (previously defined in Row) and replaced it by 2 empty() methods that are more explicit and help the developer (when using IDE code completion facility).

With this API, we have solved most problems existing with V0.1 with one exception: one can still call label() several times in a grid row although it is not supported by the library.

Now the example code shown in the first installment can be rewritten
layout.row().label(lbl1).add(field1).add(field2).add(field3);
layout.row().label(lbl2).add(field4).add(field5);
layout.row() .add(field6).add(field7);
layout.emptyRow(18);
layout.centerRow().add(ok).add(cancel);
to be compared with the previous snippet:
layout.row().label(lbl1).add(field1).add(field2).add(field3);
layout.row().label(lbl2).add(field4).add(field5);
layout.row() .add(field6).add(field7);
layout.row().height(18);
layout.row().center().add(ok).add(cancel);
The difference may not be remarkable here, but believe me you will see the difference when using your preferred IDE (code completion will not show you a plethora of irrelevant methods for the current context).

That's it for the API of V0.9. In next installment, I will describe 2 evolutions (due to adding new features) made to the API in 2 snapshots of the future V1.0.

That's it for this time. Enjoy!

No comments:

Post a Comment