我正在试图弄清楚是否有一种干净的方式来做到这一点。我想设计一个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方式。
答案 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)
我可能会将职责分为两部分:
然后枚举将存储该类的实例,只要构造函数保持不变,就可以向类中添加新方法,而无需修改不同的枚举。请注意,类必须是不可变的,否则您的枚举将不具有常量值。
您可以像:
一样使用它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 枚举类型的一些要点。
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原则会让你的设计难以打破,但当你以一种你不应该(并且无法阻止)的方式调用这种方法时,它会爆炸。/ p>
这几乎与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中使用枚举类型支付的代价。