如何使用只有一个参数在实例化之间不同的Java Enum DRY?

时间:2013-11-20 16:32:36

标签: java enums

我正在试图弄清楚是否有一种干净的方式来做到这一点。我想设计一个ENUM来维护我的应用程序中不同组件的常量值列表。每个枚举将具有相同的配置和相同的参数,但至少会因组件名称而不同。

在普通的Java类中,我可以在基本抽象类中构建所有基本逻辑/代码,并使每个组件常量扩展抽象类并仅填充其自己的相关信息。但是,Java枚举不允许扩展现有类。

我能做些什么来避免将所有常量推送到单个枚举(ugggg!)中,或者每次为每个不同的组件重新创建相同的枚举类?在这种情况下绝对不是DRY,但我不知道如何避免这个问题。

快速使用案例远离我的脑海。假设我想在Enum中保留所有请求映射的列表,以便在我的应用程序的其他地方使用。相当容易设计一个说:

的枚举
public enum RequestMapping {
    INDEX("index"),
    GET_ALL_USERS( "getAllUsers");

    private String requestMapping = "/users";
    private String path;

    RatesURI( String path ){
        this.path = path;
    }

    public String getRequestMapping(){
        return requestMapping;
    }

    public String getPath(){
        return path;
    }

    public String getFullRequestPath(){
        return requestMapping + "/" + path;
    }
}

使用RequestMapping.GET_ALL_USERS.getFullRequestPath()变得很容易。

现在,如果我想在每个控制器的基础上创建这个枚举,我将不得不重新创建整个Enum类并为每个枚举更改“requestMapping”值。当然,这个枚举几乎没有代码,所以重复它并不困难,但这个概念仍然存在。理论上“干净”的方法是使用包含所有方法的抽象AbstractRequestMapping类型,包括抽象的getRequestMapping()方法,并且只有扩展的Enums实现特定于控制器的getReqeuestMapping()。当然,由于Enums无法扩展,我无法想到这样做的非DRY方式。

3 个答案:

答案 0 :(得分:4)

您是否考虑过将Enum作为通用参数的类扩展?这是一个非常灵活的机制。

public class Entity<E extends Enum<E> & Entity.IE> {
  // Set of all possible entries.
  // Backed by an EnumSet so we have all the efficiency implied along with a defined order.
  private final Set<E> all;

  public Entity(Class<E> e) {
    // Make a set of them.
    this.all = Collections.unmodifiableSet(EnumSet.<E>allOf(e));
  }

  // Demonstration.
  public E[] values() {
    // Make a new one every time - like Enum.values.
    E[] values = makeTArray(all.size());
    int i = 0;
    for (E it : all) {
      values[i++] = it;
    }
    return values;
  }

  // Trick to make a T[] of any length.
  // Do not pass any parameter for `dummy`.
  // public because this is potentially re-useable.
  public static <T> T[] makeTArray(int length, T... dummy) {
    return Arrays.copyOf(dummy, length);
  }

  // Example interface to implement.
  public interface IE {
    @Override
    public String toString();

  }

}

class Thing extends Entity<Thing.Stuff> {

  public Thing() {
    super(Stuff.class);
  }

  enum Stuff implements Entity.IE {
    One,
    Two;

  }
}

您可以通过多种不同方式将实现的性质传递给父类 - 为简单起见,我使用enum.class

您甚至可以让enum实现一个界面,如您所见。

values方法仅供演示。一旦您有权访问父类中的Set<E>,您就可以通过扩展Entity来提供各种功能。

答案 1 :(得分:1)

我可能会将职责分为两部分:

  1. 关于如何构建请求的逻辑,并将其放入不可变类。
  2. 存储在枚举中的每个请求的实际配置
  3. 然后枚举将存储该类的实例,只要构造函数保持不变,就可以向类中添加新方法,而无需修改不同的枚举。请注意,类必须是不可变的,否则您的枚举将不具有常量值。

    您可以像:

    一样使用它
    ServiceRequest.INDEX.getRequest().getFullRequestPath()
    

    使用这些课程:

    public interface RequestType {
      Request getRequest();
    }
    public class Request {
        private final String requestMapping;
        private final String path;
    
        RatesURI(String requestMapping, String path){
            this.requestMappint = requestMapping;
            this.path = path;
        }
    
        public String getRequestMapping(){
            return requestMapping;
        }
    
        public String getPath(){
            return path;
        }
    
        public String getFullRequestPath(){
            return requestMapping + "/" + path;
        }
    }
    
    public enum ServiceRequest implements RequestType {
        INDEX("index"),
        GET_ALL_USERS( "getAllUsers");
    
        private final Request;
    
        ServiceRequest(String path) {
            request = new Request("users/", path)
        }
    
        public String getRequest{
            return request;
        }
    }
    

答案 2 :(得分:1)

我认为你应该问自己的是为什么你想要使用枚举。首先,我们可以查看使Java 枚举类型的一些要点。

具体地

  • Java enum是一个扩展java.lang.Enum
  • 的类
  • 枚举常量是该类的静态最终实例。

有一些特殊的语法可以使用它们,但这就是它们归结为的全部内容。因为在特殊语法之外不允许实例化新的Enum实例(即使使用反射,枚举类型返回零构造函数),以下也被确保为真:

  • 他们只能将 实例化为封闭类的静态最终成员
  • 因此,实例显式常量
  • 作为奖励,它们可以切换。

它真正归结为它的内容是什么使它们比这里更简单的OOP设计更受欢迎。可以轻松创建一个简单的RequestMapping类:

/* compacted to save space */
public class RequestMapping {
    private final String mapping, path;

    public RequestMapping(String mapping, String path) {
        this.mapping = mapping; this.path = path;
    }

    public String getMapping() {
        return mapping; }
    public String getPath() {
        return path; }
    public String getFullRequestPath() {
        return mapping + "/" + path;
    }
}

可以轻松扩展以分解重复的代码:

public class UserMapping extends RequestMapping {
    public UserMapping(String path) {
        super("/users", path);
    }
}

/* where ever appropriate for the constants to appear */
public static final RequestMapping INDEX = new UserMapping("index"),
                           GET_ALL_USERS = new UserMapping("getAllUsers");

但我认为有一些关于枚举的东西对你的设计很有吸引力,例如它们的实例受到高度控制的原则。枚举不能像上面的类一样无所不能地创建。也许重要的是,没有合理的方式来创建虚假实例。当然,任何人都可以通过一个无效的路径来写一个枚举,但你可以肯定没有人会这样做#34;意外地#34;。

遵循Java&#34;外部类的静态实例&#34;在枚举设计中,可以设计一种访问修饰符结构,其通常遵循与Enum相同的规则集。但是,有两个问题我们无法轻易解决。

两个问题

  • 受保护的修饰符允许包访问。

最初可以通过将Enum-analog放在自己的包装中来轻松克服。问题变成了扩展时要做的事情。 扩展类的同一个包中的类将能够在任何地方再次访问构造函数。

使用此功能取决于您希望创建新实例的严格程度,以及相反,设计最终的清晰程度。不能只是因为只有少数地方可以做错事。

  • 静态成员不是多态的。

Enum通过不可扩展来克服这个问题。枚举类型有一个静态方法values,它出现&#34;继承&#34;因为编译器会为您插入它。多态,DRY和具有一些静态特征意味着您需要子类型的实例。

击败这两个问题取决于您希望设计的严格程度,以及相反,您希望实现的可读性和稳定性。试图违反OOP原则会让你的设计难以打破,但当你以一种你不应该(并且无法阻止)的方式调用这种方法时,它会爆炸。

第一个解决方案

这几乎与Java enum模型完全相同,但可以扩展:

/* 'M' is for 'Mapping' */
public abstract class ReturnMapping<M extends ReturnMapping> {

    /* ridiculously long HashMap typing */
    private static final HashMap <Class<? extends ReturnMapping>, List<ReturnMapping>>
                VALUES = new HashMap<Class<? extends ReturnMapping>, List<ReturnMapping>>();

    private final String mapping, path;

    protected Mapping(String mapping, String path) {
        this.mapping = mapping;
        this.path = path;

        List vals = VALUES.get(getClass());

        if (vals == null) {
            vals = new ArrayList<M>(2);
            VALUES.put(getClass(), vals);
        }

        vals.add(this);
    }

    /* ~~ field getters here, make them final ~~ */

    protected static <M extends ReturnMapping> List<M>(Class<M> rm) {
        if (rm == ReturnMapping.class) {
            throw new IllegalArgumentException(
                    "ReturnMapping.class is abstract");
        }

        List<M> vals = (List<M>)VALUES.get(rm);

        if (vals == null) {
            vals = new ArrayList<M>(2);
            VALUES.put(rm, (List)vals);
        }

        return Collections.unmodifiableList(vals);
    }
}

现在扩展它:

public final class UserMapping extends ReturnMapping<UserMapping> {
    public static final UserMapping INDEX = new UserMapping("index");
    public static final UserMapping GET_ALL_USERS = new UserMapping("getAllUsers");

    private UserMapping(String path) {
        super("/users", path);
    }

    public static List<UserMapping> values() {
        return values(UserMapping.class);
    }
}

巨大的静态HashMap允许几乎所有values工作都在超类中静态完成。由于静态成员没有被正确地继承,因此这是最接近维护值列表而不在子类中执行它。

注意Map有两个问题。首先,您可以使用values致电ReturnMapping.class。映射不应包含该键(该类是抽象的,并且映射仅添加到构造函数中),因此需要对其进行操作。您也可以插入&#34;虚拟&#34;而不是抛出异常。该密钥的空列表。

另一个问题是,在实例化子类的实例之前,可以在超类上调用values。如果在访问子类之前完成此操作,HashMap将返回null。静态问题!

此设计还存在另一个主要问题,因为该类可以在外部实例化。如果它是嵌套类,则外部类具有私有访问权限。您还可以扩展它并使构造函数公开。这导致设计#2。

第二种解决方案

在这个模型中,常量是一个内部类,外部类是一个用于检索新常量的工厂。

/* no more generics--the constants are all the same type */
public abstract class ReturnMapping {

    /* still need this HashMap if we want to manage our values in the super */
    private static final HashMap <Class<? extends ReturnMapping>, List<Value>>
                VALUES = new HashMap<Class<? extends ReturnMapping>, List<Value>>();

    public ReturnMapping() {
        if (!VALUES.containsKey(getClass())) {
            VALUES.put(getClass(), new ArrayList<Value>(2));
        }
    }

    public final List<Value> values() {
        return Collections.unmodifiableList(VALUES.get(getClass()));
    }

    protected final Value newValue(String mapping, String path) {
        return new Value(getClass(), mapping, path);
    }

    public final class Value {
        private final String mapping, path;

        private Value(
                    Class type,
                    String mapping,
                    String path) {

            this.mapping = mapping;
            this.path = path;

            VALUES.get(type).add(this);
        }

        /* ~~ final class, field getters need not be ~~ */
    }
}

扩展它:

public class UserMapping extends ReturnMapping {
    public static final Value INDEX, GET_ALL_USERS;

    static {
        UserMapping factory = new UserMapping();

        INDEX = factory.newValue("/users", "index");
        GET_ALL_USERS = factory.newValue("/users", "getAllUsers");
    }
}

工厂模型很好,因为它解决了两个问题:

  • 只能在扩展类中创建实例。

任何人都可以创建一个新工厂,但只有类本身才能访问newValue方法。 Value的构造函数是私有的,因此只能使用此方法创建新常量。

  • new UserMapping().values()强制在返回值之前实例化值。

在这方面没有更多潜在的错误。并且ReturnMapping类是空的并且在Java中实例化新对象很快,所以我不担心开销。您还可以轻松地为列表创建静态字段,或者添加静态方法,例如在解决方案#1中(尽管这会降低设计的一致性)。

有一些缺点:

  • 无法返回子类型values列表。

既然常量值没有扩展,它们都是同一个类。无法使用泛型来返回不同类型的列表。

  • 无法轻易区分哪个子类型Value是常量。

但是这可以编程。你可以将拥有的类添加为字段。仍然不稳定。

它的总和

可以将铃声和口哨声添加到这两个解决方案中,例如覆盖toString,以便返回实例的名称。 Java的枚举为你做了这件事,但我个人做的第一件事就是覆盖这种行为,以便它返回更有意义(和格式化)的东西。

这两种设计都提供了比常规抽象类更多的封装,最重要的是比Enum更灵活。尝试使用Enum进行多态性是在圆孔中的OOP方形钉。较少的多态性是在Java中使用枚举类型支付的代价。