【问题标题】:enums vs. classes: code duplication, composition, extension, generics枚举与类:代码重复、组合、扩展、泛型
【发布时间】:2013-04-19 13:08:03
【问题描述】:

注意:我意识到这与问题eliminating duplicate Enum code 非常相似,但我认为讨论这个问题可能很有用 分开,因为我还提到了扩展等问题 (子类化)和泛型。如果这看起来多余,我深表歉意。

我在前段时间编写的程序中实现了一些优化。当前尝试消除由于缺乏对 Java 中的抽象枚举的支持而发生的代码重复(......以及次优设计,我承认这一点)。

这是当前问题的简化版本。 有一个接口 IConfigResourceDescriptor 提供了将一些 XML 加载到某些类(实现 IResourceRoot)所需的信息:

public interface IConfigResourceDescriptor {
    String getResourceName();
    String getResourceLocation();
    <T extends IResourceRoot> Class<T> getRootClass();
    IConfigType getConfigType();
    ...
}

我有不同的配置集,可以由不同的应用程序或同一应用程序的不同部分定义,具体取决于那里的需要。 例如这里我展示了 2 个集合,ResourceDescriptorAResourceDescriptorB,这样一个更面向业务逻辑 (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 包装器方法。 我没有任何外部合同,因此我不会通过更改此合同来破坏任何客户。

问题是……

这样可以吗?或者有什么我没有预料到的可能会出现问题的地方吗?您在这种方法中看到任何大的(或小的,或中等的)禁忌吗?有更好的方法吗?

这是反复出现的问题,我永远不确定正确的方法是什么。
我试图寻找有关使用枚举的好的和坏的做法,但是(令人惊讶的是)我只发现了非常基本的东西或例子,这些东西太具体而对我来说没有用。如果您可以推荐我可以阅读的关于该主题的好文章/博客/书籍,我也会很感激。
谢谢!


泛型

使用类而不是枚举的另一个优点是我可以使它(和接口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;
    }
    ...
}

【问题讨论】:

    标签: java inheritance enums composition code-duplication


    【解决方案1】:

    我看不出你的方法有任何问题。您可能会遇到一个潜在的未来问题,但应该很容易避免。

    当您希望向您的enums 添加功能时,就会出现此问题。处理附加到enum 的数据在代理对象中而功能在enum 中这一事实可能会有点令人不安,尤其是在功能依赖于数据的情况下。

    我喜欢你的方法,并打算自己研究一下它的用途。

    同时,您可能还想尝试其他两种技术。

    与您的类似,但enum 是一个对象的内部类,它继承自一个泛型超类,其泛型类型包括enumHere 是包含示例的帖子。这里的技巧是将枚举的EnumSet 传递给父构造函数,以便它可以提供通用功能。

    您可能对第二种技术不太感兴趣,因为它实际上涉及将enum 和另一个对象进行代理组合。 Here 是最近发布的示例。

    【讨论】:

    • 感谢您的回答。我想我会坚持我的实施,但我非常喜欢你在first link 中发布的内容。如果我遇到这种情况,我一定会记住这一点。在将其标记为答案之前,我会稍等片刻,但它可能是那个:) 再次感谢!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-10-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-06-28
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多