Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to use Java Enums being DRY with only a single parameter different between instantiations?

Tags:

java

enums

I'm trying to figure out if there is a clean way of doing this. I want to design an ENUM to maintain a list of constant values for different components in my application. Each enum would have the same configuration and same parameters, but would differ at the very least by component name.

In a normal Java class, I could build all the basic logic/code in a base abstract class, and have each component constants extend the abstract class and populate only its own pertinent information. However, Java enums do not allow extending existing classes.

Is there something I can do to avoid having to either push all my constants in a single Enum (ugggg!) or recreate the same enum class each time for each differing component? Definitely not DRY in that case, but I do not know how to avoid the issue.

For a quick use-case example off the top of my head. Say I want to keep a list of all my request mappings in an Enum for use elsewhere in my application. Fairly easy to design an enum 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 becomes easy to use RequestMapping.GET_ALL_USERS.getFullRequestPath().

Now if I want to create this enum on a per-controller basis, I would have to recreate the entire Enum class and change the "requestMapping" value for each one. Granted, this enum has nearly no code in it, so duplicating it would not be difficult, but the concept still remains. The theoretical "clean" way of doing this would be to have an abstract AbstractRequestMapping type that contained all the methods, including an abstract getRequestMapping() method, and only have the extending Enums implement the controller-specific getReqeuestMapping(). Of course, since Enums cannot be extended, I can't think of a non DRY way of doing this.

like image 201
Eric B. Avatar asked Nov 20 '13 16:11

Eric B.


People also ask

Can you use == to compare enums in Java?

equals method uses == operator internally to check if two enum are equal. This means, You can compare Enum using both == and equals method.

Can we store different data types in enum?

I must answer a resounding no because actually you can't. Enums have their own data type and each enum is essentially a new data type.

Should enums be in a separate file Java?

You don't have to declare an enum in a separate file.


2 Answers

Have you considered extending a class that takes Enum as a generic parameter? It 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 up to the parent class in many different ways - I use enum.class for simplicity.

You can even make the enum implement an interface as you can see.

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

like image 112
OldCurmudgeon Avatar answered Oct 21 '22 09:10

OldCurmudgeon


I will probably split the responsibilities into two parts:

  1. Logic about how a request is structured, and put that into an immutable class.
  2. Actual configurations of each request, stored in enums

The enum will then store an instance of that class, you can add new methods to the class, without modifying the different enums, as long as the constructor remains the same. Note that the class must be immutable, or your enum will not have a constant value.

You can use it like the:

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;
    }
}
like image 31
Tobber Avatar answered Oct 21 '22 11:10

Tobber