When creating API's I find the bounded nature of enums to be self-documenting when used as parameters such as keys in key-value pairs, as opposed to traditional usage of strings. Additionally it removes the possibility of bugs from a consumer of the API making a typo in a string key. However, what if you are releasing an API that has both a defined set of key values and the possibility for unknown key values. I find that sometimes you have the need for an "extensible" enumerated type. For example, in one case we were delivering an Android library for usage in client software with a server-side back-end component that could add functionality over time. I wanted to provide the capability for a customer to use new keys delivered via new server functionality even though the library itself was unaware of these keys at the time of the library creation. However, I still wanted the self-documenting, less error-prone way of specifying keys provided by an enumerated type. To get both behaviors, we introduced a simple construct that we called DynamicEnum. A DynamicEnum is a class, not a Java enum, but uses String constants to enumerate the known values.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public abstract class DynamicEnum { | |
private final String key; | |
protected DynamicEnum(String key) { | |
this.key = key.toUpperCase(Locale.US); | |
validate(key); | |
} | |
private static void validate(String key) { | |
if (StringUtils.isEmpty(key)) { | |
throw new IllegalArgumentException("Key should not be null or empty"); | |
} | |
} | |
protected final String getKey() { | |
return key; | |
} | |
@Override | |
public boolean equals(Object o) { | |
return o != null && o instanceof DynamicEnum && ((DynamicEnum) o).key.equals(key); | |
} | |
@Override | |
public int hashCode() { | |
return key.hashCode(); | |
} | |
@Override | |
public String toString() { | |
return key; | |
} | |
} | |
public static final class AppCommandType extends DynamicEnum { | |
public static final AppCommandType ALARM = new AppCommandType("ALARM"); | |
public static final AppCommandType CALENDAR = new AppCommandType("CALENDAR"); | |
public static final AppCommandType CALLING = new AppCommandType("CALLING"); | |
// etc | |
/** | |
* Creates {@link AppCommandType}. | |
* | |
* @param key key from server response which is associated with this {@link AppCommandType}. | |
*/ | |
public AppCommandType(String key) { | |
super(key.toUpperCase(Locale.US)); | |
} | |
} | |
// Later from another class, usage of the key | |
static{ | |
registerDefaultHandler(AppCommandType.ALARM,AlarmAppHandler.class); | |
registerDefaultHandler(AppCommandType.ANDROID_INTENT,AndroidIntentAppHandler.class); | |
registerDefaultHandler(AppCommandType.CALENDAR,CalendarAppHandler.class); | |
registerDefaultHandler(AppCommandType.CALLING,VoiceDialHandler.class); | |
//etc | |
} | |
//Looks and acts like an enum when used with a known type |