注意:我意识到这与消除重复枚举代码的问题非常相似,但我认为单独讨论这个问题可能很有用,因为我还提到了扩展(子类化)和泛型等问题。如果这看起来多余,我深表歉意。
我在前段时间编写的程序中实现了一些优化。当前的一个尝试消除由于缺乏对 Java 中的抽象枚举的支持而发生的代码重复(......以及次优设计,我承认这一点)。
这是当前问题的简化版本。有一个接口IConfigResourceDescriptor
提供了将一些 XML 加载到某些类(实现IResourceRoot
)的必要信息:
public interface IConfigResourceDescriptor {
String getResourceName();
String getResourceLocation();
<T extends IResourceRoot> Class<T> getRootClass();
IConfigType getConfigType();
...
}
我有不同的配置集,可以由不同的应用程序或同一应用程序的不同部分定义,具体取决于那里的需要。例如,这里我展示了 2 组ResourceDescriptorA
和ResourceDescriptorB
,这样一组更面向业务逻辑 ( ResourceDescriptorA
),另一组更面向用户界面 ( ResourceDescriptorB
)。如您所见,它们的代码是相同的;它们分开的唯一原因是保持这两组配置相互独立是有意义的,但从它们的实现的角度来看,它们是完全相同的。
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;
}
...
}
解决方案
我的想法是将代码移动到辅助类并从枚举中引用它。此外,为了避免有很多方法只是将调用包装到实际IConfigResourceDescriptor
(帮助程序类),我定义了一个新接口IConfigResourceDescriptorProvider
,它只返回IConfigResourceDescriptor
,然后可用于获取配置的实际描述。枚举现在实现IConfigResourceDescriptorProvider
而不是IConfigResourceDescriptor
.
包含实际实现的新助手类:
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;
}
...
}
枚举实现的新接口。它只是返回实际的描述符:
public interface IConfigResourceDescriptorProvider {
IConfigResourceDescriptor getResourceDescriptor();
}
枚举被简化:构造函数ConfigResourceDescriptor
使用参数的值创建一个(帮助类)。ConfigResourceDescriptor
是实际的描述符。
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;
}
}
这ConfigResourceDescriptor
是一个类而不是枚举的事实也意味着它可以被扩展以提供额外的功能。也许那时我会有一个ResourceDescriptorProviderC
实例化AdvancedConfigResourceDescriptor
而不是ConfigResourceDescriptor
并且能够提供更多功能:
public class AdvancedConfigResourceDescriptor extends ConfigResourceDescriptor {
// additional methods
}
事实上,现在枚举没有实现IConfigResourceDescriptor
,而是IConfigResourceDescriptorProvider
意味着我曾经做的地方......
ResourceDescriptorProviderA.MODEL.getResourceName();
...现在我必须做...
ResourceDescriptorProviderA.MODEL.getResourceDescriptor().getResourceName();
...但是资源描述符的使用在我的代码中非常集中,我只需要进行一些更改。我更喜欢这样,而不是必须在实现接口的所有枚举中定义所有包装器方法。我没有任何外部合同,所以我不会通过改变这个来破坏任何客户。
问题是...
这可以吗,或者有什么我没有预料到可能会出现问题的地方吗?您在这种方法中看到任何大的(或小的,或中等的)禁忌吗?有更好的方法吗?
这是反复出现的问题,我永远不确定正确的方法是什么。
我试图寻找有关使用枚举的好的和坏的做法,但是(令人惊讶的是)我只发现了非常基本的东西或例子,这些东西太具体而对我有用。如果你能推荐我能读到的关于这个主题的好文章/博客/书籍,我也会很感激。
谢谢!
泛型
使用类而不是枚举的另一个优点是我可以使它(和接口IConfigResourceDescriptor
)通用:
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;
}
...
}