Mixing-in an Enum
By darcy on May 15, 2007
Effective Java discusses two variants of the type-safe enum pattern, one that allows subclassing and one that does not. The enum language construct added in JDK 5 only provides the non-subclassing variant (because supporting subclassing would have confusing interactions with switch statements and other enum features). However, having the enum class implement a mixin interface can restore some of the third-party extensibility of the subclassing variant. (In Java, a mixin interface is used to capture some secondary behavior a class can provide; in other languages, mixins can carry implementations too.)
During JSR 199, Peter, Jon, and I were having a meeting discussing a few API design issues. The
JavaFileMangager has a notion of locations of where to look for files. While there are a number of standard locations that needed to be supported for a Java compiler, such as classpath and sourcepath, other kinds of tools need to be able to define their own locations too. So using an enum for the known compiler locations would be structured, but not support the needed extensibility while using a list of known strings to lookup locations would allow extensions, but be very unstructured. The solution we came up with was to use an enum and a name-based lookup.
The solution has a few parts:
Define a simple interface with the needed functionality. In this case, the
JavaFileManager.Locationinterface just has two methods,
isOutputLocation. The first is used to retrieve the key of the location and the second is used by the file manager.
Declare an enum implementing the interface where the enum constants represent the known values. For example,
JavaFileManager.Locationand has constants for the classpath, sourcepath, output directory, and other locations already in use by
javac. Since enum types are classes, they can implement interfaces.
Include a factory method mapping from names to objects implementing the interface. For JSR 199, The
locationFormethod maps from strings to locations. A factory method for the interface, unlike the
valueOfmethod on the enum, is able to support aliasing of names and return objects from types that implement the interface other than the enum. That is, it would be possible for the factory method to map multiple names to the same object rather than just one canonical name. Including a factory isn't strictly necessary, but is would be a bit onerous to force clients to write their own factory or use anonymous classes, etc. to create non-standard values.
This combination of a enum for known values and an interface for extensibility provides a good alternative to string-based provider lookups.