In a recent coding project I fell foul of Java enumerations. They seem like a very good idea, and indeed they are when used properly. However, there is sometimes a tendency to overuse them - something unfortunately learnt from personal experience.
Intially Java had no inbuilt concept of enumerated types. Developers often found themselves inadequately simulating them by creating a bunch on integer constants. Proper enumerations were added to Java in JDK1.5 with the enum keyword and developers rejoiced (or at least I did). The following example from the Java Language Guide shows how they are used:
public enum Rank { DEUCE, THREE, FOUR, FIVE, SIX,
SEVEN, EIGHT, NINE, TEN, JACK, QUEEN, KING, ACE }
public enum Suit { CLUBS, DIAMONDS, HEARTS, SPADES }
Actually, the above is just the basic usage pattern. Java enums can have
data and behavior, as shown in the Planet
example below (from the same
guide).
They can also implement abstract methods directly at the definition (see
the guide) but they cannot be subclassed.
public enum Planet {
MERCURY (3.303e+23, 2.4397e6),
VENUS (4.869e+24, 6.0518e6),
EARTH (5.976e+24, 6.37814e6),
MARS (6.421e+23, 3.3972e6),
JUPITER (1.9e+27, 7.1492e7),
SATURN (5.688e+26, 6.0268e7),
URANUS (8.686e+25, 2.5559e7),
NEPTUNE (1.024e+26, 2.4746e7),
PLUTO (1.27e+22, 1.137e6);
private final double mass; // in kilograms
private final double radius; // in meters
Planet(double mass, double radius) {
this.mass = mass;
this.radius = radius;
}
public double mass() { return mass; }
public double radius() { return radius; }
// universal gravitational constant (m3 kg-1 s-2)
public static final double G = 6.67300E-11;
public double surfaceGravity() {
return G * mass / (radius * radius);
}
public double surfaceWeight(double otherMass) {
return otherMass * surfaceGravity();
}
}
I think the first (Rank
& suit
) example is correct usage of enums -
essentially treating them as related constant values. In my opinion, the
second example is problematic. Here the Planet
enum is acting
similarly to a class with each instance being like an object, with data
and associated behaviour. At the level of the example this may seem
acceptable - not much is happening here. However, code rarely stays
static. Requirements change and thus the code changes to handle
different functionality. It is not hard to imagine more data being
required for each Planet
. Object-oriented classes are designed to help
handle this change. Enumerations are not and there is no inheritance,
nor is it possible to pass non static constructor arguments. As a
result, as the functionality of an enum becomes more “object-like”, the
code supporting it becomes increasingly messy.
The general rule is that linked constants should be enumerations, but anything more than that should probably be a class.
I have broken this rule myself and lived to regret it. A project I
recently worked on aggregated a number of different financial markets
and presented a single interface for downstream clients. This project
used quite a few enumerations. For example, there were enums to define
the market defined type of a financial instrument and another for its
pricing method. These worked fine, they were mutually exclusive values
read from the markets essentially as constants. There was also an enum
for the markets themselves. At first this was fine, each market was
treated the same. However, as the system expanded to new markets and
extra functionality, differences between markets began to appear. More
and more logic began to hang off the market enum. Some was data stored
on the market enum. Most was determined by the environment and was set
by configuration settings. This logic ended up in various special
classes constructed by
Factory classes
switching on market. Indeed there was quite a bit of logic in the system
that switched code path depending on market. The code would definitely
have worked better had there been a Market
class rather than
enumeration. Refactoring all the code to use a class rather than a
enumeration was a big job in an important working system.