Note: I realize that this is very similar to the question eliminating duplicate Enum code, 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.
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).
Here's a simplified version of the current problem.
There's an interface IConfigResourceDescriptor
that provides the necessary information to load some XMLs into certain classes (which implement IResourceRoot
):
public interface IConfigResourceDescriptor {
String getResourceName();
String getResourceLocation();
<T extends IResourceRoot> Class<T> getRootClass();
IConfigType getConfigType();
...
}
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, ResourceDescriptorA
and ResourceDescriptorB
, such that one is more oriented towards the business logic (ResourceDescriptorA
) and the other towards the user interface (ResourceDescriptorB
). 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.
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<? extends IResourceRoot> rootClass;
private IConfigType configType;
private <T extends IResourceRoot> ResourceDescriptorA(String resourceName,
String resourceLocation, Class<T> 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 <T extends IResourceRoot> Class<T> getRootClass() {
return rootClass;
}
public IConfigType getConfigType() {
return configType;
}
...
}
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<? extends IResourceRoot> rootClass;
private IConfigType configType;
private <T extends IResourceRoot> ResourceDescriptorB(String resourceName,
String resourceLocation, Class<T> 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 <T extends IResourceRoot> Class<T> getRootClass() {
return rootClass;
}
public IConfigType getConfigType() {
return configType;
}
...
}
Solution
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 IConfigResourceDescriptor
(the helper class), I've defined a new interface IConfigResourceDescriptorProvider
which simply returns the IConfigResourceDescriptor
, which then can be used to get the actual description of the configuration.
The enums now implement IConfigResourceDescriptorProvider
rather than IConfigResourceDescriptor
.
New helper class which contains the actual implementation:
public class ConfigResourceDescriptor implements IConfigResourceDescriptor {
private String resourceName;
private String resourceLocation;
private Class<? extends IResourceRoot> rootClass;
private IConfigType configType;
public <T extends IResourceRoot> ConfigResourceDescriptor(String resourceName,
String resourceLocation, Class<T> 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 <T extends IResourceRoot> Class<T> getRootClass() {
return rootClass;
}
public IConfigType getConfigType() {
return configType;
}
...
}
New interface implemented by the enums. It simply returns the actual descriptor:
public interface IConfigResourceDescriptorProvider {
IConfigResourceDescriptor getResourceDescriptor();
}
The enums are simplified: the constructor creates a ConfigResourceDescriptor
(helper class) using the values of the parameters. The ConfigResourceDescriptor
is the actual descriptor.
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 <T extends IResourceRoot> ResourceDescriptorA(String resourceName,
String resourceLocation, Class<T> rootClass, IConfigType configType) {
resourceDescriptor = new ConfigResourceDescriptor(resourceName,
resourceLocation, rootClass, configType);
}
public IConfigResourceDescriptor getResourceDescriptor() {
return resourceDescriptor;
}
}
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 <T extends IResourceRoot> ResourceDescriptorB(String resourceName,
String resourceLocation, Class<T> rootClass, IConfigType configType) {
resourceDescriptor = new ConfigResourceDescriptor(resourceName,
resourceLocation, rootClass, configType);
}
public IConfigResourceDescriptor getResourceDescriptor() {
return resourceDescriptor;
}
}
The fact that ConfigResourceDescriptor
is a class and not an enum also means it can be extended to provide extra functionality. Maybe then I'll have a ResourceDescriptorProviderC
that instantiates AdvancedConfigResourceDescriptor
instead of ConfigResourceDescriptor
and will be able to provide more functionality:
public class AdvancedConfigResourceDescriptor extends ConfigResourceDescriptor {
// additional methods
}
The fact that now the enumerations do not implement IConfigResourceDescriptor
but rather IConfigResourceDescriptorProvider
means that where I used to do...
ResourceDescriptorProviderA.MODEL.getResourceName();
...now I have to do...
ResourceDescriptorProviderA.MODEL.getResourceDescriptor().getResourceName();
...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 all the wrapper methods in all the enums that implement the interface.
I don't have any external contract so I won't break any client by changing this.
And the question is...
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?
This is something that has come up repeatedly and I'm never sure what the correct approach is.
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.
Thank you!
Generics
Another advantage of using a class instead of an enum is that I can make it (and the interface IConfigResourceDescriptor
) generic:
public interface IConfigResourceDescriptor<T extends IResourceRoot> {
String getResourceName();
String getResourceLocation();
Class<T> getRootClass();
IConfigType getConfigType();
...
}
public class ConfigResourceDescriptor<T extends IResourceRoot> implements IConfigResourceDescriptor<T> {
private String resourceName;
private String resourceLocation;
private Class<T> rootClass;
private IConfigType configType;
public ConfigResourceDescriptor(String resourceName,
String resourceLocation, Class<T> 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<T> getRootClass() {
return rootClass;
}
public IConfigType getConfigType() {
return configType;
}
...
}