Enhancing Enums
As already mentioned elsewhere, I like Java enums a lot, because you can enhance them easily to make them more useful. But the examples I gave until now are quite complex. So here are some easier ones.
A Very Simple Example: I18n
Often I want to display properties in a human-readable form, and this is often true for enum values.
The standard toString()
method of an enum
will return its value. This is not perfect, especially
for foreigners which don’t talk English (the language I’m using for programming). Luckily I already
have a system for internationalization (i18n for short). For what we need to know now there is
one static method String I18n.getString(String)
which takes a string tag and returns an associated
string depending on the current Locale
(it’s in the de·caff Commons
in case you are interested).
Starting from
public enum Test
{
One,
Two,
Three
}
enhancement is perfectly simple:
public enum Test
{
One("tagOne"),
Two("tagTwo"),
Three("tagThree");
private final String i18nTag;
Test(String tag)
{
i18nTag = tag;
}
public String getDescription()
{
return I18n.getString(i18nTag);
}
}
Of course you’ll have to define the relation between the i18n tags and the localized texts elsewhere,
but the above is enough to have a human-readable text for each enum value just by calling its
getDescription()
method. And in the real world I’d have added some javadoc comments and @NotNull
annotations.
Not As Simple: Include Algorithms
If you want to add behavior there is always one step to be done: as enums are final, the individual values cannot have different behavior. But the old programming rule that if something cannot be done introduce an indirection comes to help here. Usually you’ll define or reuse an interface, and give each enum value a different implementation of this interface.
The following example hopefully illustrates why this can be most useful. Assuming you want to handle graphical leader which consist of a text and an arrow which points to a given place. The arrow itself consist of a line and an arrow head. When drawing, you want to allow your users to change the drawing order. So you define the following 6 constants:
public enum LeaderDrawingOrder
{
HeadLineText,
HeadTextLine,
LineHeadText,
LineTextHead,
TextHeadLine,
TextLineHead
}
You’ll probably want to add i18n descriptions as in the first example.
Now for each implementation which draws the leader you’ll have to do something like the following:
switch (drawingOrder) {
case HeadLineText:
drawHead();
drawLine();
drawText();
break;
case HeadTextLine:
drawHead();
drawText();
drawLine();
break;
case LineHeadText:
drawLine();
drawHead();
drawText();
break;
case LineTextHead:
drawLine();
drawText();
drawHead();
break;
case TextHeadLine:
drawText();
drawHead();
drawLine();
break;
case TextLineHead:
drawText();
drawLine();
drawHead();
break;
}
If you are only doing this once, you could bite your way through it, but it is tedious and error-prone.
But as promised there is more generic way. There is no restriction what should be handled so we’ll add a method
called order()
to the enum which is expected to get three parameters of a any type and return
an array of this type where they are correctly ordered:
public <T> T[] order(T head, T line, T text)
{
// ???
}
Using a generic method does not restrict the user, and allows easier usage as e.g using java.lang.Object
because
the returned array has a useful type in the given circumstances.
To fill out the method an indirection is introduced, using a private interface:
public enum LeaderDrawingOrder
{
HeadLineText(new Sorter() {
public <T> T[] order(T head, T line, T text)
{
return new T[] { head, line, text };
}
}),
HeadTextLine(new Sorter() {
public <T> T[] order(T head, T line, T text)
{
return new T[] { head, text, line };
}
}),
LineHeadText(new Sorter() {
public <T> T[] order(T head, T line, T text)
{
return new T[] { line, head, text };
}
}),
LineTextHead(new Sorter() {
public <T> T[] order(T head, T line, T text)
{
return new T[] { line, text, head };
}
}),
TextHeadLine(new Sorter() {
public <T> T[] order(T head, T line, T text)
{
return new T[] { text, head, line };
}
}),
TextLineHead(new Sorter() {
public <T> T[] order(T head, T line, T text)
{
return new T[] { text, line, head };
}
});
private interface Sorter
{
<T> T[] order(T head, T line, T text);
}
private final Sorter sorter;
LeaderDrawingOrder(Sorter sorter)
{
this.sorter = sorter;
}
public <T> T[] order(T head, T line, T text)
{
return sorter.order(head, line, text);
}
}
Adapting the above switch
is currently not as nice as could be, but Java 9 will help when it comes out sooner or later:
for (Runnable r : drawingOrder.order(new Runnable() { public void run() { drawHead(); }},
new Runnable() { public void run() { drawLine(); }},
new Runnable() { public void run() { drawText(); }})) {
r.run();
}
But if you already have items head
, line
and text
which each implement a common interface (a preferable
design) it directly looks much better:
for (Drawable d : drawingOrder.order(head, line, text)) {
d.draw();
}
This is much clearer as the switch
statement, and you’ll only have to get the ordering right once when writing
the enum. The design is clearer, too, as the ordering is exactly where it belongs: in the place describing it.
N.B.: in some cases it is preferable to use a public (possibly external) interface and make the enum itself implement it. In the above example just making the interface public and the enum implement it would already be enough. But in this case it is not necessary, as the interface is just an implementation detail of no interest to the outside world, so it should stay hidden.