Thursday, October 30, 2008

DesignGridLayout: in search of the perfect API (part 1)

This is the first installment of a short series (4 or 5 posts) telling the story of DesignGridLayout API.

From the first beginning, DesignGridLayout was meant to be easy to use in order to build Swing forms; its API was meant to make the use of any graphical designer totally useless. It was also meant to give code maintainers the possibility to "see" the UI while only browsing the source code.

For that, it has used a fluent API (some may use the term "DSL" or "Domain Specific Language" in this context) so that you could, in one line of code, define one row of your UI, eg:
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 code above would give the following result in a dialog:


As mentioned above, this series is about the evolution of that API. Prior to giving details on API history and changes motivation, I must thank Joshua Bloch for his excellent book "Effective Java, 2nd Edition" (I read it this summer, I had never read the first edition before) which I recommend to every Java programmer and particularly to library developers; this book has helped me a lot in thinking about providing a better API to DesignGridLayout users.

Initial release 0.1

Version 0.1 could be seen as a good prototype of implementing canonical grids in a Swing layout with a rather good API.

The library was then made of 4 classes only (all public)!

public class DesignGridLayout implements LayoutManager {
...
public Row row() {...}
}

public class Row {
public static final JComponent EMPTY = ...;
public Row label(JLabel label) {...}
public Row add(JComponent child) {...}
public Row add(JComponent... children) {...}
public Row add(JComponent child, int span) {...}
public Row left() {...}
public Row center() {...}
public Row right() {...}
public Row grid() {...}
public Row height(int height) {...}
...
}

public enum RowAlign {...}

public class RowItem {...}

Quite straightforward, wasn't it? However, this API suffered from several problems (don't forget that was a kind of prototype at that time):
  1. Some public classes (RowItem, RowAlign) should have been hidden because they were implementation details. By exposing these classes, you make them part of the API, preventing potential future changes in the implementation (don't forget that when designing an API, you should strive to make it as good as can be, but also as narrow as possible, because it will be hard to change afterwards -without breaking compatibility).
  2. Many public methods from DesignGridLayout and Row classes should have been hidden because they served no purpose to the user of the library, once again they were implementation details that were unintentionally added to the API, leading to a crippled API (in the sense that all those useless methods appear and are proposed by IDE code-completion feature)
  3. This API made it possible to change the type of a row as many times as you want (by calling e.g. layout.row().grid().left().right().center().add(...);)
  4. You could add a label to a non-grid row (which was not supported by the implementation) so this could give wrong impressions to users of the library
  5. You could add a component with a column span > 1 in a non-grid row (not supported)
  6. You could specify the height of any row (which is not advised for a proper look). Usage of the height(int) method was intended as in layout.row().height(18) (no component added to the row).
To summarize the main issues with this API:
  • many methods or classes should not be public (they should be either private or package-private)
  • the API allows code that is not supported at runtime and may give the code writer/reader a false idea of how the UI looks like
In the next installment, I will talk about the API changes in version 0.9 (current official version of DesignGridLayout).

That's all for today!

No comments:

Post a Comment