Note that there are some explanatory texts on larger screens.

plurals
  1. POenums vs. classes: code duplication, composition, extension, generics
    primarykey
    data
    text
    <blockquote> <p>Note: I realize that this is very similar to the question <a href="https://stackoverflow.com/questions/77213/eliminating-duplicate-enum-code">eliminating duplicate Enum code</a>, but I think it may be useful to discuss this separately since I'm also mentioning issues like extension (sublclassing) and generics. I apologize if this seems redundant.</p> </blockquote> <p>I'm implementing some optimizations in a program that I wrote a while ago. The current one attempts to eliminate code duplication that occurred as a result of the lack of support for abstract enums in Java (...and of sub-optimal design, I'll admit it).</p> <p>Here's a simplified version of the current problem. There's an interface <code>IConfigResourceDescriptor</code> that provides the necessary information to load some XMLs into certain classes (which implement <code>IResourceRoot</code>):</p> <pre class="lang-java prettyprint-override"><code>public interface IConfigResourceDescriptor { String getResourceName(); String getResourceLocation(); &lt;T extends IResourceRoot&gt; Class&lt;T&gt; getRootClass(); IConfigType getConfigType(); ... } </code></pre> <p>I have different sets of configurations that can be defined by different applications or different parts of the same application depending on what is required there. For example here I show 2 sets, <code>ResourceDescriptorA</code> and <code>ResourceDescriptorB</code>, such that one is more oriented towards the business logic (<code>ResourceDescriptorA</code>) and the other towards the user interface (<code>ResourceDescriptorB</code>). As you can see their code is identical; the only reason they're separate is that it makes sense to keep these 2 sets of configurations independent from each other, but from the point of view of their implementation they're exactly the same.</p> <pre class="lang-java prettyprint-override"><code>public enum ResourceDescriptorA implements IConfigResourceDescriptor { MODEL("model.xml", "config/", Model.class, ConfigTypeA.MODEL), RULES("rules.xml", "config/validation/", Rules.class, ConfigTypeA.RULES), HELP("help.xml", "config/", Help.class, ConfigTypeA.HELP); private String resourceName; private String resourceLocation; private Class&lt;? extends IResourceRoot&gt; rootClass; private IConfigType configType; private &lt;T extends IResourceRoot&gt; ResourceDescriptorA(String resourceName, String resourceLocation, Class&lt;T&gt; rootClass, IConfigType configType) { this.resourceName = resourceName; this.resourceLocation = resourceLocation; this.rootClass = rootClass; this.configType = configType; } public String getResourceName() { return resourceName; } public String getResourceLocation() { return resourceLocation; } public &lt;T extends IResourceRoot&gt; Class&lt;T&gt; getRootClass() { return rootClass; } public IConfigType getConfigType() { return configType; } ... } </code></pre> <pre class="lang-java prettyprint-override"><code>public enum ResourceDescriptorB implements IConfigResourceDescriptor { DIALOGS("dialogs.xml", "config/", Dialogs.class, ConfigTypeB.DIALOGS), FORMS("forms.xml", "config/", Forms.class, ConfigTypeB.FORMS), MENUS("menus.xml", "config/", Menus.class, ConfigTypeB.MENUS); private String resourceName; private String resourceLocation; private Class&lt;? extends IResourceRoot&gt; rootClass; private IConfigType configType; private &lt;T extends IResourceRoot&gt; ResourceDescriptorB(String resourceName, String resourceLocation, Class&lt;T&gt; rootClass, IConfigType configType) { this.resourceName = resourceName; this.resourceLocation = resourceLocation; this.rootClass = rootClass; this.configType = configType; } public String getResourceName() { return resourceName; } public String getResourceLocation() { return resourceLocation; } public &lt;T extends IResourceRoot&gt; Class&lt;T&gt; getRootClass() { return rootClass; } public IConfigType getConfigType() { return configType; } ... } </code></pre> <p><br></p> <h2>Solution</h2> <p>My idea is to move the code to a helper class and reference that from the enums. Also, to avoid having lots of methods that just wrap invocations to the actual <code>IConfigResourceDescriptor</code> (the helper class), I've defined a new interface <code>IConfigResourceDescriptorProvider</code> which simply returns the <code>IConfigResourceDescriptor</code>, which then can be used to get the actual description of the configuration. The enums now implement <code>IConfigResourceDescriptorProvider</code> rather than <code>IConfigResourceDescriptor</code>.</p> <p>New helper class which contains the actual implementation:</p> <pre class="lang-java prettyprint-override"><code>public class ConfigResourceDescriptor implements IConfigResourceDescriptor { private String resourceName; private String resourceLocation; private Class&lt;? extends IResourceRoot&gt; rootClass; private IConfigType configType; public &lt;T extends IResourceRoot&gt; ConfigResourceDescriptor(String resourceName, String resourceLocation, Class&lt;T&gt; rootClass, IConfigType configType) { this.resourceName = resourceName; this.resourceLocation = resourceLocation; this.rootClass = rootClass; this.configType = configType; } public String getResourceName() { return resourceName; } public String getResourceLocation() { return resourceLocation; } public &lt;T extends IResourceRoot&gt; Class&lt;T&gt; getRootClass() { return rootClass; } public IConfigType getConfigType() { return configType; } ... } </code></pre> <p>New interface implemented by the enums. It simply returns the actual descriptor:</p> <pre class="lang-java prettyprint-override"><code>public interface IConfigResourceDescriptorProvider { IConfigResourceDescriptor getResourceDescriptor(); } </code></pre> <p>The enums are simplified: the constructor creates a <code>ConfigResourceDescriptor</code> (helper class) using the values of the parameters. The <code>ConfigResourceDescriptor</code> is the actual descriptor.</p> <pre class="lang-java prettyprint-override"><code>public enum ResourceDescriptorProviderA implements IConfigResourceDescriptorProvider { MODEL("model.xml", "config/", Model.class, ConfigTypeA.MODEL), RULES("rules.xml", "config/validation/", Rules.class, ConfigTypeA.RULES), HELP("help.xml", "config/", Help.class, ConfigTypeA.HELP); private IConfigResourceDescriptor resourceDescriptor; private &lt;T extends IResourceRoot&gt; ResourceDescriptorA(String resourceName, String resourceLocation, Class&lt;T&gt; rootClass, IConfigType configType) { resourceDescriptor = new ConfigResourceDescriptor(resourceName, resourceLocation, rootClass, configType); } public IConfigResourceDescriptor getResourceDescriptor() { return resourceDescriptor; } } </code></pre> <pre class="lang-java prettyprint-override"><code>public enum ResourceDescriptorProviderB implements IConfigResourceDescriptorProvider { DIALOGS("dialogs.xml", "config/", Dialogs.class, ConfigTypeB.DIALOGS), FORMS("forms.xml", "config/", Forms.class, ConfigTypeB.FORMS), MENUS("menus.xml", "config/", Menus.class, ConfigTypeB.MENUS); private IConfigResourceDescriptor resourceDescriptor; private &lt;T extends IResourceRoot&gt; ResourceDescriptorB(String resourceName, String resourceLocation, Class&lt;T&gt; rootClass, IConfigType configType) { resourceDescriptor = new ConfigResourceDescriptor(resourceName, resourceLocation, rootClass, configType); } public IConfigResourceDescriptor getResourceDescriptor() { return resourceDescriptor; } } </code></pre> <p>The fact that <code>ConfigResourceDescriptor</code> is a class and not an enum also means it can be extended to provide extra functionality. Maybe then I'll have a <code>ResourceDescriptorProviderC</code> that instantiates <code>AdvancedConfigResourceDescriptor</code> instead of <code>ConfigResourceDescriptor</code> and will be able to provide more functionality:</p> <pre class="lang-java prettyprint-override"><code>public class AdvancedConfigResourceDescriptor extends ConfigResourceDescriptor { // additional methods } </code></pre> <p>The fact that now the enumerations do not implement <code>IConfigResourceDescriptor</code> but rather <code>IConfigResourceDescriptorProvider</code> means that where I used to do...</p> <pre class="lang-java prettyprint-override"><code>ResourceDescriptorProviderA.MODEL.getResourceName(); </code></pre> <p>...now I have to do...</p> <pre class="lang-java prettyprint-override"><code>ResourceDescriptorProviderA.MODEL.getResourceDescriptor().getResourceName(); </code></pre> <p>...but the use of resource descriptors is very centralized in my code and I only need to make a few changes. I like this better than having to define <em>all</em> the wrapper methods in <em>all</em> the enums that implement the interface. I don't have any external contract so I won't break any client by changing this.<br> <br> </p> <h2>And the question is...</h2> <p>Is this ok or is there anything I'm not anticipating that could be problematic? Do you see any big (or small, or medium) no-nos in this approach? Is there a better approach?</p> <p>This is something that has come up repeatedly and I'm never sure what the correct approach is.<br> I tried to look for good and bad practices concerning the use of enums but (surprisingly enough) I only found very basic stuff or examples that are too specific to be of use for me. If you can recommend good articles/blogs/books that I could read on the subject I would also appreciate it.<br> Thank you!</p> <hr> <h2>Generics</h2> <p>Another advantage of using a class instead of an enum is that I can make it (and the interface <code>IConfigResourceDescriptor</code>) generic:</p> <pre class="lang-java prettyprint-override"><code>public interface IConfigResourceDescriptor&lt;T extends IResourceRoot&gt; { String getResourceName(); String getResourceLocation(); Class&lt;T&gt; getRootClass(); IConfigType getConfigType(); ... } </code></pre> <pre class="lang-java prettyprint-override"><code>public class ConfigResourceDescriptor&lt;T extends IResourceRoot&gt; implements IConfigResourceDescriptor&lt;T&gt; { private String resourceName; private String resourceLocation; private Class&lt;T&gt; rootClass; private IConfigType configType; public ConfigResourceDescriptor(String resourceName, String resourceLocation, Class&lt;T&gt; rootClass, IConfigType configType) { this.resourceName = resourceName; this.resourceLocation = resourceLocation; this.rootClass = rootClass; this.configType = configType; } public String getResourceName() { return resourceName; } public String getResourceLocation() { return resourceLocation; } public Class&lt;T&gt; getRootClass() { return rootClass; } public IConfigType getConfigType() { return configType; } ... } </code></pre>
    singulars
    1. This table or related slice is empty.
    plurals
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. This table or related slice is empty.
 

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