Domanda

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;
    }
    ...
}
È stato utile?

Soluzione

I cannot see any problems with your approach. There is one potential future issue you may come across but it should be easily avoided.

The issue will arise when you wish to add functionality to your enums. It may then be a little unnerving deal with the fact that the data attached to the enum is in a surrogate object while the functionality is in the enum especially if the functionality depends on the data.

I like your approach and intend to investigate its use for myself.

Meanwhile there are two other techniques you may like to experiment with.

One similar to yours but the enum is an inner-class of an object which inherits from a generic superclass whose generic type includes the enum. Here is a post containing an example. The trick here is to pass an EnumSet of the enum to the parent constructor so it can provide common functionality.

The second technique may be of less interest to you because it actually involves making a proxy combination of an enum and another object. Here is a recently posted example.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top