Enumeration sub-types
Font for text and font for date are both fonts. Colors for corresponding elements are both colors. Colors and fonts are properties of some theme. You need some constants to read out properties (i.e. fonts or colors) from the theme. The safest and clearest way to have them broken into hierarchy of sub-types. The article is not only about fonts and colors, it’s about general approach to enumeration sub-types and testing of their applications.
In our programming practice we often need to deal with different constants. And it’s not rare that the constants belong to one logical group. It is clear that for someone not to break his neck the constants should be better grouped together into classes. The initial feeling is that everything is already invented here.
Imagine that you have a task to add the visual themes to the application. The theme is a set of key-value pairs describing various aspects of GUI. Though both the keys and values are strings it would be stupid to give something like this to the forefront programmers:
String getProperty(String key)
Everyone knows that the possible keys can be defined in constants, but do you really believe that it could save from accepting arbitrary names? However, here’s how the constants approach could look like:
public static final String KEY_TEXT_FOREGROUND = "text.foreground";
public static final String KEY_TEXT_BACKGROUND = "text.background";
String getProperty(String key)
There are several weak places here:
-
The key can be arbitrary string which can potentially hold no meaning to the theme and the method will need to throw
IllegalArgumentException
to let the application know about that problem. -
We have all keys as strings without semantic weight. Colors, fonts, sizes and other values you may need will have string keys, which aren’t allowing you to judge beforehand what the output will look like.
-
The value returned is also in the string. It means that the acceptor will need to take care of converting it into something meaningful by itself. It’s not a good idea to spread the conversion functionality that wide.
To address the first issue we need to introduce the class ThemeKey
. The class presents a simple enumeration. But for everything on Earth, why do we need that enumeration-like class if we already have enumerations in Java 1.5? Wait a second and everything will get clear.
So, next our move is to make our theme property getter look like the following.
Object getProperty(ThemeKey key)
The ThemeKey
will look like this:
/**
* The class is enumeration of keys for Theme.
*/
public final class ThemeKey
{
/** Text foreground. */
public static final ThemeKey TEXT_FOREGROUND = new ThemeKey("text.foreground");
/** Text background. */
public static final ThemeKey TEXT_BACKGROUND = new ThemeKey("text.background");
private String key;
/**
* Creates a an enumeration element with given key name.
*
* @param key the key name.
*/
private ThemeKey(String key)
{
this.key = key;
}
/**
* Returns the name of the key.
*
* @return key name.
*/
public String getKey()
{
return key;
}
}
Now the calling code will look much better, just because we have fixed set of keys.
Object textForeground = theme.getProperty(ThemeKey.TEXT_FOREGROUND);
We are getting really close to the second issue (loss of semantics in keys). While we limit caller to only known keys the caller has to get value in Object
to allow returns of any type. The next step is to introduce several methods for getting each type of values from themes: fonts and colors, for example.
Font getFont(ThemeKey key)
Color getColor(ThemeKey key)
The narrow place here is that the caller may call the getColor()
and give ThemeKey.TEXT_FOREGROUND
as key. Nonsense, but still possible. Here comes the solution – we introduce the sub-classes of ThemeKey which bring more semantical information about the type of value.
/**
* The class is enumeration of keys for Theme.
*/
public final class ThemeKey
{
private String key;
/**
* Creates a an enumeration element with given key name.
*
* @param key the key name.
*/
private ThemeKey(String key)
{
this.key = key;
}
/**
* Returns the name of the key.
*
* @return key name.
*/
public String getKey()
{
return key;
}
/**
* Group for color keys.
*/
public static final class Color extends ThemeKey
{
/** Text foreground. */
public static final Color TEXT_FOREGROUND = new Color("text.foreground");
/** Text background. */
public static final Color TEXT_BACKGROUND = new Color("text.background");
private Color(String key)
{
super(key);
}
}
/**
* Group for font keys.
*/
public static final class Font extends ThemeKey
{
/** Text font. */
public static final Font TEXT = new Font("text.font");
private Font(String key)
{
super(key);
}
}
}
And now come the changes in definition of the theme class.
/**
* Simple theme definition.
*/
public interface ITheme
{
/** Returns font for a given key. */
Font getFont(ThemeKey.Font key);
/** Returns color for a given key. */
Color getColor(ThemeKey.Color key);
}
Now everything is clear. The developers have no way to make a mistake in specifying the keys, your application is safe and everyone is happy.