I think you should be asking yourself why you want to use enumerations for this. First, we can look at some of the points that make Java the enumerated types that they are.
Specifically
- Java
enum is a class that extends java.lang.Enum . - Enumeration constants are static final instances of this class.
There is a special syntax for using them, but that's all they come down to. Since instances of new Enum instances are forbidden outside of special syntax (even when reflected, enum types return null constructors), it is also guaranteed that this is true:
- They can only be created as static end members of the enclosing class.
- Therefore, instances are clearly constant.
- As a bonus, they can switch.
The fact that it really comes down to the fact that it concerns enumerations makes them preferable to the simpler design of OOP. You can easily create a simple RequestMapping class:
public class RequestMapping { private final String mapping, path; public RequestMapping(String mapping, String path) { this.mapping = mapping; this.path = path; } public String getMapping() { return mapping; } public String getPath() { return path; } public String getFullRequestPath() { return mapping + "/" + path; } }
What can be easily expanded to break repeating code:
public class UserMapping extends RequestMapping { public UserMapping(String path) { super("/users", path); } } public static final RequestMapping INDEX = new UserMapping("index"), GET_ALL_USERS = new UserMapping("getAllUsers");
But I guess something has to do with enumerations that are attractive to your design, such as the principle in which they are controlled. Enums cannot be created all willingly, as it can be higher. Perhaps it is important that there is no plausible way to create false instances. Of course, anyone can come and write in an enumeration with an unacceptable path, but you can be sure that no one will do this “by accident”.
After the Java project "static instances of the outer class", an access modifier structure can be developed, which, as a rule, is executed using the same rule as Enum. However, there are two problems that we cannot easily cope with.
Two problems
- A protected modifier allows access to packages.
This can be easily ported initially by putting Enum-analog in your package. The problem is what to do when it expands. Classes in the same extended class package will be able to access the constructors again potentially anywhere.
Working with this depends on how strict you are to create new instances, and, conversely, how clear the design is. There cannot be a whole mess of areas, so only a few places can do the wrong thing.
- Static elements are not polymorphic.
Enum overcomes this without expanding. Enum types have a static values method that looks "inherited" because the compiler inserts it for you. Being polymorphic, dry, and having some static functions, you will need subtype instances.
Defeating these two problems depends on how rigorous you want your design to be, and, conversely, how readable and stable you want your implementation to be. Trying to ignore the principles of OOP will give you a design that is hard to break, but explodes completely when you call this method what you shouldn't (and can't prevent).
First decision
This is almost identical to the Java enum model, but can be extended:
public abstract class ReturnMapping<M extends ReturnMapping> { private static final HashMap <Class<? extends ReturnMapping>, List<ReturnMapping>> VALUES = new HashMap<Class<? extends ReturnMapping>, List<ReturnMapping>>(); private final String mapping, path; protected Mapping(String mapping, String path) { this.mapping = mapping; this.path = path; List vals = VALUES.get(getClass()); if (vals == null) { vals = new ArrayList<M>(2); VALUES.put(getClass(), vals); } vals.add(this); } protected static <M extends ReturnMapping> List<M>(Class<M> rm) { if (rm == ReturnMapping.class) { throw new IllegalArgumentException( "ReturnMapping.class is abstract"); } List<M> vals = (List<M>)VALUES.get(rm); if (vals == null) { vals = new ArrayList<M>(2); VALUES.put(rm, (List)vals); } return Collections.unmodifiableList(vals); } }
Now expand it:
public final class UserMapping extends ReturnMapping<UserMapping> { public static final UserMapping INDEX = new UserMapping("index"); public static final UserMapping GET_ALL_USERS = new UserMapping("getAllUsers"); private UserMapping(String path) { super("/users", path); } public static List<UserMapping> values() { return values(UserMapping.class); } }
The huge static HashMap allows almost all of the values to work statically in the superclass. Since static members are not inherited properly, this is the closest thing you can get to maintain a list of values without doing this in a subclass.
Please note that there are two problems with the Map. First, you can call values using ReturnMapping.class . The map should not contain this key (the class is abstract, and the map is added only to the constructor), so you need to do something about this. Instead of throwing an exception, you can also insert a "dummy" empty list for this key.
Another problem is that you can call values in the superclass before the subclass is instantiated. HashMap will return null if this is done before accessing the subclass. Static problem!
There is another serious problem with this design, because the class can be created externally. If it is a nested class, the outer class has private access. You can also expand it and make the designer public. This leads to design number 2.
Second solution
In this model, constants are the inner class, and the outer class is the factory to retrieve new constants.
public abstract class ReturnMapping { private static final HashMap <Class<? extends ReturnMapping>, List<Value>> VALUES = new HashMap<Class<? extends ReturnMapping>, List<Value>>(); public ReturnMapping() { if (!VALUES.containsKey(getClass())) { VALUES.put(getClass(), new ArrayList<Value>(2)); } } public final List<Value> values() { return Collections.unmodifiableList(VALUES.get(getClass())); } protected final Value newValue(String mapping, String path) { return new Value(getClass(), mapping, path); } public final class Value { private final String mapping, path; private Value( Class type, String mapping, String path) { this.mapping = mapping; this.path = path; VALUES.get(type).add(this); } } }
Expansion:
public class UserMapping extends ReturnMapping { public static final Value INDEX, GET_ALL_USERS; static { UserMapping factory = new UserMapping(); INDEX = factory.newValue("/users", "index"); GET_ALL_USERS = factory.newValue("/users", "getAllUsers"); } }
The factory model is good because it solves two problems:
- Instances can only be created from an expanding class.
Anyone can create a new factory, but only the class itself can access the newValue method. The constructor for Value is private, so new constants can only be created using this method.
new UserMapping().values() forces values to be returned.
There are no more potential errors in this regard. And the ReturnMapping class is empty, and creating new objects in Java is fast, so I won’t worry about the overhead. You can also easily create a static field for the list or add static methods, for example, to solution # 1 (although this will smooth out the uniformity of the design).
There are several disadvantages:
- Cannot return a list of
values subtypes.
Now that the constant values are not expanded, they are all the same. You can not plunge into generics to return lists with different types.
- It is not easy to distinguish which subtype of a
Value is a constant.
But it is true that this can be programmed. You can add an owner class as a field. Still shaky.
Amount of it
Bells and whistles can be added to both of these solutions, for example, overriding toString so that it returns the instance name. The Java enum does this for you, but one of the first things I personally do is override this behavior, so it returns something more meaningful (and formatted).
Both of these projects provide more encapsulation than a regular abstract class, and, most importantly, are much more flexible than Enum. Attempting to use Enum for polymorphism is a square OOP snap in a round hole. Less polymorphism is the price of listing types in Java.