Dynamic Enumerations, revisited
Announcer: Now, it’s time for the custodian of clean code, the man who codes what he means and means what he codes, El Moffdo!
That’s correct Mr. Announcer, I am your host, designing with a mere pad and pen just to give the bugs a chance. This is the long-awaited sequel to the oft-searched topic of dynamic enumerations. Let’s go.
First, why is this sequel being written? I have learned that the general problem of a variable’s allowed values being restricted to a finite set that can grow or shrink at run-time is not a unique one; it goes by the alias of the Allowed Value Table (AVT). The quest for solutions to this problem is the source of many of the search engine referrals to this blog.
On top of this, though, is the fact that this very same problem is addressed at my place of work. The solution is AVT-inspired. The trouble is that this is where the solution begins and ends. Client code is little more than the manipulation of a lightly-abstracted database table. There is little resemblance to the language enumerations we cherish.
I gotta get off this rock
- I know it’s pure OO, but the populate() method in the DynamicEnum is a mixing of concerns. Sigh.
- DynamicEnumValueFactory doesn’t produce DynamicEnumValues; it takes in a Hashtable and populates it with DynamicEnumValues. Sigh.
- Same class: getKeysSortedOnOrder()? Doesn’t belong there, as was noted in the original post.
- DynamicEnumValueRepository doesn’t load DynamicEnumValues. In fact, just by looking at the diagram, I don’t know what the hell it does.
- I had to open the source code itself to find out, and what I found was kind of shocking. getKeys() and getValues() load the keys and values from persistence with a SELECT *, and DynamicEnumValueFactory adds them to the Hashtable in array order; basically, getKeys()[i] maps to getValues()[i]. The code is entirely dependent on persistence returning the keys and values in such a way.
I actually didn’t think it was this bad before I sat down and wrote the above litany of complaints. Yeesh.
Took your sweet time, marine?
I decided to re-write this part of the code, and my intent was to reduce the number of classes required to support one dynamic enumeration, as well as write only classes that are clear in purpose and free of kludges.
That said, two of the original classes weren’t total failures, believe it or not, and were carried over in the re-design. DynamicEnum and EnumValue:
Some differences you will notice is the selective use of package-protected access (represented by a “~”). Aside from enabling the use of an external factory in the package design, making the constructors of EnumValue and DynamicEnum package-private enabled me to avoid a cyclic dependency.
The need for such a dependency was brought on by the problem of how to determine whether a given EnumValue is valid for a certain DynamicEnum. I wrote something like this:
public class HatEnumValue
private HatDynamicEnum parent;
public void assign(HatEnumValue valueToAssign)
this.ID = valueToAssign.ID;
this.order = valueToAssign.order;
public class HatDynamicEnum
public boolean isValid(HatEnumValue value)
But if no one can get their hands on an EnumValue without first going through the DynamicEnum, and no one can get their hands on a DynamicEnum without first going through the DynamicEnumRepository, then the EnumValue they have has to be valid, by design.
Next, you’ll notice that the DynamicEnum maintains two mappings: one from name to EnumValue, and another from ID to EnumValue. I admit this is unfortunate, because the reason has nothing to do with the domain of dynamic enumerations.
It has to do with the fact that, when you use dynamic enumerations in other objects, you are storing the ID of the EnumValue, just like a foreign key. The idea of dynamic enumerations is to store the name (String) once and only once.
Therefore, when objects are loaded from persistence, you need some way of going from the ID to the EnumValue, which is what your object expects.
The way I tackled this problem during The Project brings me great shame. It was a colossal WTF collection of classes suffixed with “OrderLookup”, one per dynamic enumeration, that loaded an EnumValue from persistence, given its ID. So the classes were misnamed, not to mention that they were dependent on the fact that persistence was on the same machine as the application.
Horrible. Truly horrid.
In any event, I accept the slight corruption of the domain and memory penalty, whatever it may be, for an extra HashMap, in exchange for avoiding a dependency on system architecture and the horrible horribleness of the OrderLookup clan.
The DynamicEnumRepository is the point of access for the DynamicEnum. Notice that the DynamicEnum is not a singleton, like it was in the previous design. It is the DynamicEnumRepository‘s responsibility to manage this constraint. In the code download, you can see an implementation of the repository that does so.
Unlike the previous design, new members of the dynamic enumeration are added to the dynamic enumeration itself. It is not ideal to put this logic in the repository; the repository needs to store and retrieve the domain object it serves.
This explains the hasBeenStored member in EnumValue; not until the DynamicEnum is stored and retrieved will new members of the dynamic enumeration be visible to client code.
Finally, I submit that the DynamicEnumFactory is still dependent on the order of the lists in its argument list being correct, just like in my earlier rant against the original design. But I will say that, by grouping the three lists into one parameter list, this design seems to suggest that the three must be retrieved together. It is certainly better than getKeys() and getValues(), but a better argument that groups the three would be ideal.
Finally, here is the core design, pictured together:
There is much to be desired from this core design.
- Complex EnumValues: on The Project, dynamic enumerations were related. The PC model dynamic enumeration could be filtered based on a value in the PC make dynamic enumeration.
On The Project, I had each PC make EnumValue contain a collection of the strings of PC makes. This sidestepped the whole issue, and also muddied the domain of dynamic enumerations.
The “right way” suggests that a PC make EnumValue should contain a collection of PC model EnumValues. And that works, until you need to get the string representation of that collection. Now what?
As the core is designed, you can only get the strings for the entire enumeration. So one way is to write a rather naive (read: slow) algorithm that examines the entire PC model enumeration, looking for matches in the collection contained within the PC make EnumValue.
An alternative, but equally awful, solution is to have the PC make EnumValue hold a reference to the PC model dynamic enumeration itself, and simply pass its make value to the enumeration, which would return all of its EnumValues that match.
One better solution, from an asymptotic complexity standpoint, would be to maintain yet another mapping in the DynamicEnum: EnumValue to String. And I am opposed to this because it is a slippery slope. Soon enough, you have all sorts of perverse mappings to support functionality that is outside the scope of dynamic enumerations.
Or, we can add the name to the EnumValue, in addition to its ID and order. This would let the PC make EnumValue contain a collection of PC model EnumValues. Moreover, converting to strings, in this case, is trivial.
This would probably be the ideal solution, as long as the name of the EnumValue is stored once and only once in memory. This constraint is, I think, central to the idea of dynamic enumerations. In Java, this would involve using StringBuffer instead of String.
- Changing the order of an EnumValue: also missing from the original design is the ability to edit the orders of EnumValues, though I think this change can be made without significant, if any, changes to the class diagram presented here.
Had enough? I have, so download the code, which includes a Driver and no JUnit because I’m lazy, and refactor away.
|Announcer: You’re reading the EIP web-ring.|