Note that there are some explanatory texts on larger screens.

plurals
  1. PO
    text
    copied!<p>I think what you should be asking yourself is really <em>why</em> you want to use enums for this. First we can review some of the points that make Java <em>enumerated types</em> what they are.</p> <h2>Specifically</h2> <ul> <li>A Java <code>enum</code> is a class that extends <code>java.lang.Enum</code>.</li> <li>Enum constants are static final instances of that class.</li> </ul> <p>There is some special syntax to use them but that is all they boil down to. Because instantiating new Enum instances is disallowed outside of the special syntax (even with reflection, enum types return zero constructors) the following is also ensured to be true:</p> <ul> <li>They can <em>only</em> be instantiated as <em>static final members</em> of the enclosing class.</li> <li>The instances are therefore <em>explicitly constant</em>.</li> <li>As a bonus, they are switchable.</li> </ul> <p>What it really boils down to is what it is about the enums that makes them preferable over a simpler OOP design here. One can easily create a simple RequestMapping class:</p> <pre><code>/* compacted to save space */ 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; } } </code></pre> <p>Which can easily be extended to break down the repeated code:</p> <pre><code>public class UserMapping extends RequestMapping { public UserMapping(String path) { super("/users", path); } } /* where ever appropriate for the constants to appear */ public static final RequestMapping INDEX = new UserMapping("index"), GET_ALL_USERS = new UserMapping("getAllUsers"); </code></pre> <p>But I assume there is something about enums that is attractive to your design, such as the principle that instances of them are highly controlled. Enums cannot be created all willy-nilly like the above class can be. Perhaps it's important that there be no plausible way for spurious instances to be created. Of course anybody can come by and write in an enum with an invalid path but you can be pretty sure nobody will do it "by accident".</p> <p>Following the Java "static instances of the outer class" enum design, an access modifier structure can be devised that generally abides by the same rule set as Enum. There are, however, two problems which we can't get around easily.</p> <h2>Two Problems</h2> <ul> <li>Protected modifier allows package access.</li> </ul> <p>This can easily be surmounted initially by putting the Enum-analog in its own package. The problem becomes what to do when extending. Classes in the same package of the <em>extended class</em> will be able to access constructors again potentially anywhere.</p> <p>Working with this depends on how stringent you want to be on creating new instances and, conversely, how clear the design ends up. Can't be a whole mess of scopes just so <em>only a few places</em> can do the wrong thing.</p> <ul> <li>Static members are not polymorphic.</li> </ul> <p>Enum surmounts this by <em>not being extendable</em>. Enum types have a static method <code>values</code> that appears "inherited" because the compiler inserts it for you. Being polymorphic, DRY and having some static features means you need instances of the subtype.</p> <p>Defeating these two issues depends on how stringent you want your design to be and, conversely, how readable and stable you want your implementation to be. Trying to defy OOP principles will get you a design that's hard to break but totally explodes when you call that one method in a way you aren't supposed to (and can't prevent).</p> <h2>First Solution</h2> <p>This is almost identical to the Java enum model but can be extended:</p> <pre><code>/* 'M' is for 'Mapping' */ public abstract class ReturnMapping&lt;M extends ReturnMapping&gt; { /* ridiculously long HashMap typing */ private static final HashMap &lt;Class&lt;? extends ReturnMapping&gt;, List&lt;ReturnMapping&gt;&gt; VALUES = new HashMap&lt;Class&lt;? extends ReturnMapping&gt;, List&lt;ReturnMapping&gt;&gt;(); 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&lt;M&gt;(2); VALUES.put(getClass(), vals); } vals.add(this); } /* ~~ field getters here, make them final ~~ */ protected static &lt;M extends ReturnMapping&gt; List&lt;M&gt;(Class&lt;M&gt; rm) { if (rm == ReturnMapping.class) { throw new IllegalArgumentException( "ReturnMapping.class is abstract"); } List&lt;M&gt; vals = (List&lt;M&gt;)VALUES.get(rm); if (vals == null) { vals = new ArrayList&lt;M&gt;(2); VALUES.put(rm, (List)vals); } return Collections.unmodifiableList(vals); } } </code></pre> <p>Now extending it:</p> <pre><code>public final class UserMapping extends ReturnMapping&lt;UserMapping&gt; { 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&lt;UserMapping&gt; values() { return values(UserMapping.class); } } </code></pre> <p>The huge static HashMap allows almost all of the <code>values</code> work to be done statically in the superclass. Since static members are not properly inherited this is the closest you can get to maintaining a list of values without doing it in the subclass.</p> <p>Note there are two problems with the Map. The first is that you can call the <code>values</code> with <code>ReturnMapping.class</code>. The map should not contain that key (the class is abstract and the map is only added to in the constructor) so something needs to be done about it. Instead of throwing an exception you could also insert a "dummy" empty list for that key.</p> <p>The other problem is that you can call <code>values</code> on the superclass before the instances of the subclass are instantiated. The HashMap will return null if this is done before the subclass is accessed. Static problem!</p> <p>There is one other major problem with this design because the class can be instantiated externally. If it's a nested class, the outer class has private access. You can also extend it and make the constructor public. That leads to design #2.</p> <h2>Second Solution</h2> <p>In this model the constants are an inner class and the outer class is a factory for retrieving new constants.</p> <pre><code>/* no more generics--the constants are all the same type */ public abstract class ReturnMapping { /* still need this HashMap if we want to manage our values in the super */ private static final HashMap &lt;Class&lt;? extends ReturnMapping&gt;, List&lt;Value&gt;&gt; VALUES = new HashMap&lt;Class&lt;? extends ReturnMapping&gt;, List&lt;Value&gt;&gt;(); public ReturnMapping() { if (!VALUES.containsKey(getClass())) { VALUES.put(getClass(), new ArrayList&lt;Value&gt;(2)); } } public final List&lt;Value&gt; 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); } /* ~~ final class, field getters need not be ~~ */ } } </code></pre> <p>Extending it:</p> <pre><code>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"); } } </code></pre> <p>The factory model is nice because it solves two problems:</p> <ul> <li>Instances can only be created from within the extending class.</li> </ul> <p>Anybody can create a new factory but only the class itself can access the <code>newValue</code> method. The constructor for <code>Value</code> is private so new constants can only be created by using this method.</p> <ul> <li><code>new UserMapping().values()</code> forces the values to be instantiated before returning them.</li> </ul> <p>No more potential errors in this regard. And the ReturnMapping class is empty and instantiating new objects in Java is fast so I wouldn't worry about overhead. You can also easily create a static field for the list or add static methods such as in solution #1 (though this would deflate the design's uniformity).</p> <p>There are a couple of downsides:</p> <ul> <li>Can't return the subtyped <code>values</code> List.</li> </ul> <p>Now that the constant values are not extended they are all the same class. Can't dip in to generics to return differently-typed Lists.</p> <ul> <li>Can't easily distinguish what subtype a <code>Value</code> is a constant of.</li> </ul> <p>But it's true this could be programmed in. You could add the owning class as a field. Still shaky.</p> <h2>Sum Of It</h2> <p>Bells and whistles can be added to both of these solutions, for example overriding <code>toString</code> so it returns the name of the instance. Java's enum does that for you but one of the first things I personally do is override this behavior so it returns something more meaningful (and formatted).</p> <p>Both of these designs provide more encapsulation than a regular abstract class and most importantly are far more flexible than Enum. Trying to use Enum for polymorphism is an OOP square peg in a round hole. Less polymorphism is the price to pay for having enumerated types in Java.</p>
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload