Enum
From APIDesign
(→Infinite Set) |
(→Growing Set) |
||
Line 3: | Line 3: | ||
== Growing Set == | == Growing Set == | ||
- | + | [[Good]] [[API]] design is about having [[evolution]] plan prepared in advance. One way of evolving an [[Enum]] is to enlarge the set of its constants. As [[Blogs:AndreiBadea:EnumsInAPIs|Andrei points out]] adding new [[enum]] constant is not strictly semantically [[BackwardCompatible]], but this [[evolution]] path has been well thought in advance when [[Enum]]s were designed. | |
- | Each switch statement (over an [[Enum]]) needs to have ''default'' branch. In case new constants are added, the code ends up in there. | + | Each switch statement (over an [[Enum]]) needs to have ''default'' branch. In case new constants are added, the code ends up in there. There are going to be some changes in newer versions - the ''default'' branch may be used more and more often, but overall, there are no surprises. Both sides of the [[API]] contract - the publisher as well as consumer - are ready for this kind of [[evolution]]. |
== Infinite Set == | == Infinite Set == |
Revision as of 08:27, 10 January 2011
Java has been enhanced with enums in version 1.5. The Enums greatly simplify and standardize the way one can express the enumerated types in Java. However since their introduction I was not sure how suitable they are for API Design (I was not the only one, see few year's old Andrei's post). Today I finished one Enum related API change for NetBeans, and I felt all the pain. I guess I understand now what is wrong with Enums in APIs.
Growing Set
Good API design is about having evolution plan prepared in advance. One way of evolving an Enum is to enlarge the set of its constants. As Andrei points out adding new enum constant is not strictly semantically BackwardCompatible, but this evolution path has been well thought in advance when Enums were designed.
Each switch statement (over an Enum) needs to have default branch. In case new constants are added, the code ends up in there. There are going to be some changes in newer versions - the default branch may be used more and more often, but overall, there are no surprises. Both sides of the API contract - the publisher as well as consumer - are ready for this kind of evolution.
Infinite Set
The real problem starts when you find out that finite amount is not enough, then the Java Enum design starts to stay in the way.
This is not as rare situation as you may think. Often one starts with Singleton instance and evolves it to multiple instances in subsequent releases of the API. Doing the same with Enum is impossible.
There are online sources of modules in NetBeans applications. They can be classified according to their stability to Standard, Beta and Community. Over time it turned out that such classification is too restrictive and I got a request to allow users of the API to define their own stability categories together with associated icons. Easy task, one may think. Basically just add new Factory method, right?
Well, it would be an easy task. If the stability category class was not originally declared as Enum!
But nobody is allowed to dynamically create instances of Enums. It is forbidden by Java compiler. Even resolving to reflection does not help, as the Constructor.newInstance has a special check for Enum subclasses and throws an exception. No luck.
What can one do? Stop extending the Enum class? But that is horribly backward incompatible! Before that people could assign instances of the class to Enum variables. Or use EnumSet with instances of that class. After stopping to extend the Enum marker class, nothing like this is no longer possible.
Options? At most to provide AlternativeBehavior:
- design new type to represent the new functionality
- optionally change the enum definition to implement the type (but then the type has to be Java interface, it cannot be a class!)
- duplicate all the methods that deal with the enum to also accept or produce the new type (this has to be done transitively)
In my case, this was exactly what I had to do. Only with one problem. The enum was in signature of an interface, and I could not change that compatibly, so I would need to design new interface. Introducing the AlternativeBehavior is transitive change - just to point out the complexity.
The Enum Problem
Shall Enum be used when designing APIs? Probably yes, but only with care. One has to be aware that it is almost impossible to switch from its finite set of values to infinite number of instances in future.
The root cause of the problems seem to be the existence of a marker super class that every enum automatically inherits from. Knowing all I know now, it would be better to use [DelegationAndComposition]] rather than inheritance. If the compiler emitted code which inherits directly from Object, it would be much easier to replace the enum in class definition by class, while remaining compatible. With approach like this, it would be harder to properly type EnumMap, but who needs proper types in this case anyway? It would be enough to have Collections.efficientMap(Class<?> keys, Class<?> values) factory method to support the same use case.
The Enum design as of Java 5 is associated with significant evolution flaws. When designing APIs, use Enums with care!
<comments/>