注意:我意识到这与问题eliminating duplicate Enum code非常相似,但我认为讨论这个问题可能会有用 因为我也提到扩展等问题 (sublclassing)和泛型。如果这看起来多余,我道歉。
我正在我刚才写的程序中实现一些优化。当前的一个试图消除由于缺乏对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();
...但是我的代码中资源描述符的使用非常集中,我只需要进行一些更改。我比在所有实现接口的枚举中定义 all 包装器方法更好。
我没有任何外部合同,所以我不会通过改变这个来打破任何客户
这可以,还是有什么我没想到可能有问题?你看到这种方法有什么大的(或小的,中等的)no-nos吗?有更好的方法吗?
这是反复出现的事情,我不知道正确的做法是什么 我试图寻找关于使用枚举的好的和坏的做法但是(令人惊讶的是)我只发现了非常基本的东西或例子,这些东西或例子对我来说太具体了。如果你能推荐我能读到的关于这个主题的好文章/博客/书籍,我也会很感激 谢谢!
使用类而不是枚举的另一个好处是我可以使它(和接口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;
}
...
}
答案 0 :(得分:1)
我看不出你的方法有任何问题。您可能会遇到一个潜在的未来问题,但应该很容易避免。
如果您希望为enum
添加功能,则会出现此问题。然后,附加到enum
的数据在代理对象中,而功能在enum
中,尤其是在功能取决于数据的情况下,可能会有点令人不安。
我喜欢你的方法,并打算自己研究它的用途。
与此同时,您还可以尝试其他两种技术。
一个类似于你的,但enum
是一个对象的内部类,它继承自泛型类,其泛型类型包含enum
。 Here是一个包含示例的帖子。这里的技巧是将枚举的EnumSet
传递给父构造函数,以便它可以提供通用功能。
第二种技术可能对您不太感兴趣,因为它实际上涉及对enum
和另一个对象进行代理组合。 Here是最近发布的示例。