How to use Java Enums DRY with a single parameter other than instances?

I am trying to figure out if there is a clean way to do this. I want to create ENUM to maintain a list of constant values ​​for different components in my application. Each enumeration will have the same configuration and the same parameters, but will differ, at least from the name of the component.

In a regular Java class, I could build all the base logic / code in a base abstract class, and each component constant extends the abstract class and fills only its own relevant information. However, Java enumerations do not allow the distribution of existing classes.

Is there something I can do to not push all of my constants in one Enum (ugggg!) Or to create the same enum class for every other component each time? Definitely not DRY in this case, but I don't know how to avoid the problem.

For a quick example of use from the top of the head. Let's say I want to keep a list of all my query mappings in Enum for use elsewhere in the application. It's pretty easy to create an enumeration that says:

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; } } 

It is easy to use RequestMapping.GET_ALL_USERS.getFullRequestPath ().

Now, if I want to create this enumeration for each controller, I will have to recreate the entire Enum class and change the "requestMapping" value for each of them. Of course, this listing has almost no code, so duplicating it will not be easy, but the concept still remains. A theoretical “clean” way to do this would be to have an abstract type AbstractRequestMapping containing all methods, including the abstract getRequestMapping () method, and only extended Enums implement the controller-specific getReqeuestMapping () method. Of course, since Enums cannot be extended, I cannot think that this is NOT a DRY way to do this.

+6
source share
3 answers

Do you consider an extension of a class that takes Enum as a generic parameter? This is an amazingly flexible mechanism.

 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; } } 

You can pass the nature of your implementation to the parent class in many ways - I use enum.class for simplicity.

You can even make Enum implement an interface, as you can see.

The values method is for demonstration purposes only. When you have access to Set<E> in the parent class, you can provide all sorts of functionality by simply extending Entity .

+4
source

I will probably divide the responsibilities into two parts:

  • The logic is about how the query is structured, and put it in a class immutable .
  • The actual configurations of each request stored in enums

Then the enumeration will store an instance of this class, you can add new methods to the class without changing different enumerations if the constructor remains the same. Note that the class must be immutable or your listing will not have a constant value.

You can use it like:

 ServiceRequest.INDEX.getRequest().getFullRequestPath() 

With these classes:

 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; } } 
+1
source

I think you should be asking yourself why you want to use enumerations for this. First, we can look at some of the points that make Java the enumerated types that they are.

Specifically

  • Java enum is a class that extends java.lang.Enum .
  • Enumeration constants are static final instances of this class.

There is a special syntax for using them, but that's all they come down to. Since instances of new Enum instances are forbidden outside of special syntax (even when reflected, enum types return null constructors), it is also guaranteed that this is true:

  • They can only be created as static end members of the enclosing class.
  • Therefore, instances are clearly constant.
  • As a bonus, they can switch.

The fact that it really comes down to the fact that it concerns enumerations makes them preferable to the simpler design of OOP. You can easily create a simple RequestMapping class:

 /* 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; } } 

What can be easily expanded to break repeating code:

 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"); 

But I guess something has to do with enumerations that are attractive to your design, such as the principle in which they are controlled. Enums cannot be created all willingly, as it can be higher. Perhaps it is important that there is no plausible way to create false instances. Of course, anyone can come and write in an enumeration with an unacceptable path, but you can be sure that no one will do this “by accident”.

After the Java project "static instances of the outer class", an access modifier structure can be developed, which, as a rule, is executed using the same rule as Enum. However, there are two problems that we cannot easily cope with.

Two problems

  • A protected modifier allows access to packages.

This can be easily ported initially by putting Enum-analog in your package. The problem is what to do when it expands. Classes in the same extended class package will be able to access the constructors again potentially anywhere.

Working with this depends on how strict you are to create new instances, and, conversely, how clear the design is. There cannot be a whole mess of areas, so only a few places can do the wrong thing.

  • Static elements are not polymorphic.

Enum overcomes this without expanding. Enum types have a static values method that looks "inherited" because the compiler inserts it for you. Being polymorphic, dry, and having some static functions, you will need subtype instances.

Defeating these two problems depends on how rigorous you want your design to be, and, conversely, how readable and stable you want your implementation to be. Trying to ignore the principles of OOP will give you a design that is hard to break, but explodes completely when you call this method what you shouldn't (and can't prevent).

First decision

This is almost identical to the Java enum model, but can be extended:

 /* '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); } } 

Now expand it:

 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); } } 

The huge static HashMap allows almost all of the values to work statically in the superclass. Since static members are not inherited properly, this is the closest thing you can get to maintain a list of values ​​without doing this in a subclass.

Please note that there are two problems with the Map. First, you can call values using ReturnMapping.class . The map should not contain this key (the class is abstract, and the map is added only to the constructor), so you need to do something about this. Instead of throwing an exception, you can also insert a "dummy" empty list for this key.

Another problem is that you can call values in the superclass before the subclass is instantiated. HashMap will return null if this is done before accessing the subclass. Static problem!

There is another serious problem with this design, because the class can be created externally. If it is a nested class, the outer class has private access. You can also expand it and make the designer public. This leads to design number 2.

Second solution

In this model, constants are the inner class, and the outer class is the factory to retrieve new constants.

 /* 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 ~~ */ } } 

Expansion:

 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"); } } 

The factory model is good because it solves two problems:

  • Instances can only be created from an expanding class.

Anyone can create a new factory, but only the class itself can access the newValue method. The constructor for Value is private, so new constants can only be created using this method.

  • new UserMapping().values() forces values ​​to be returned.

There are no more potential errors in this regard. And the ReturnMapping class is empty, and creating new objects in Java is fast, so I won’t worry about the overhead. You can also easily create a static field for the list or add static methods, for example, to solution # 1 (although this will smooth out the uniformity of the design).

There are several disadvantages:

  • Cannot return a list of values subtypes.

Now that the constant values ​​are not expanded, they are all the same. You can not plunge into generics to return lists with different types.

  • It is not easy to distinguish which subtype of a Value is a constant.

But it is true that this can be programmed. You can add an owner class as a field. Still shaky.

Amount of it

Bells and whistles can be added to both of these solutions, for example, overriding toString so that it returns the instance name. The Java enum does this for you, but one of the first things I personally do is override this behavior, so it returns something more meaningful (and formatted).

Both of these projects provide more encapsulation than a regular abstract class, and, most importantly, are much more flexible than Enum. Attempting to use Enum for polymorphism is a square OOP snap in a round hole. Less polymorphism is the price of listing types in Java.

+1
source

Source: https://habr.com/ru/post/958496/


All Articles