Java – Using Enum as a fully functional object, using dynamic dispatch like in this case means misusing it or is it not a good design?

Using Enum as a fully functional object, using dynamic dispatch like in this case means misusing it or is it not a good design?… here is a solution to the problem.

Using Enum as a fully functional object, using dynamic dispatch like in this case means misusing it or is it not a good design?

I’ve implemented a role enumeration with role definitions with ranks. For the getAssignableRoles() part, my colleague said it was an abuse or over-engineering of Enum, noting that this form was unreadable.

public enum Role {
    USER("ROLE_user", 1),
    CUSTOMER("ROLE_customer", 2),
    PRODUCT_OWNER("ROLE_product_manager", 3) {
        @Override
        public List<Role> getAssignableRoles() {
            return getLowerRankedRoles().stream().filter(e -> !e.equals(CUSTOMER_ADMIN)).collect(Collectors.toList());
        }
    },
    CUSTOMER_ADMIN("ROLE_customer_admin", 3) { 
        @Override
        public List<Role> getAssignableRoles() {
            return getLowerRankedRoles().stream().filter(e -> !e.equals(PRODUCT_OWNER)).collect(Collectors.toList());
        }
    },
    USER_MANAGER("ROLE_user_manager", 5),
    ADMIN("ROLE_admin", 99);

private final String value;
    private final int rank;

public String getValue() {
        return value;
    }
    public int getRank() {
        return rank;
    }

Role(String value, int rank) {
        this.value = value;
        this.rank = rank;
    }

public static Role findByAbbr(String abbr) {
        return Arrays.stream(values()).filter(value -> value.value.equals(abbr)).findFirst().orElse(UNKNOWN);
    }

public String getExactValue() {
        return value.replaceFirst("^ROLE_", "");
    }

 Each role has a distinct grant rank but for some roles even if they have same grant level their role assigning changes in the context.
    public List<Role> getAssignableRoles() {
        return getLowerRankedRoles();
    }

protected List<Role> getLowerRankedRoles() {
        return Arrays.stream(values()).filter(value -> value.rank <= rank).collect(Collectors.toList());
    }

public static Predicate<String> isInRealm() {
         return (String role) -> (Arrays.stream(values()).anyMatch(value -> value.value.equals(role)));
    }

}

What I want to do is to be able to call it like this in the client code

Role.findByAbbr(role).getAssignableRoles()

Is it a good practice or design to use enumerations in this way? Can we get most of the dynamic scheduling functionality from Enum?

Solution

This question is actually more interesting than it seems at first glance. I see two differences here:

  1. Your role does not have a mutable state;
  2. You need access to a list of all the different roles, which is fixed.

Regarding point (1), if a value is immutable, it is called a constant, and Java enum is exactly like this: a set of constant values (<a href=”https://docs.oracle.com/javase/tutorial/java/javaOO/enum.html” rel=”noreferrer noopener nofollow”> see the official Java tutorial ).

Point (2) refers to the power of Java enum's values() predefined method. In fact, the implementation of Role.findByAbbr(abbr) would be a mess if other strategies were used.

Let me elaborate on this further. If you don’t want your client code to be able to create new roles, a common strategy is to leverage package-friendly visibility ranges. Declare an abstract class Role in package com.example.role.

package com.example.role;

public abstract class Role {
    private int rank;
    private String value;

Role(String value, int rank) {
        this.value = value;
        this.rank = rank;
    }

 Other methods...
}

Note that there is no explicit visibility modifier on the constructor (so it’s package-friendly). Only classes within the same package can call the constructor of class Role, so only inheritance within the same package.

package com.example.role;

public class UserRole extends Role {
    private static final UserRole instance = new UserRole();

private UserRole() {
        super("ROLE_user", 1);
    }

public UserRole getInstance() {
        return instance;
    }
}

We’re trying to emulate your enum using pure Java classes, so we’re also using the singleton pattern to have one instance per role.

Everything is simple, but when it comes to the values() predefined method, the situation changes.

package com.example.role;

public final class Roles {
    private static Collection<Role> values = Collections.unmodifiableList(Arrays.asList(
        UserRole.getInstance(),
        CustomerRole.getInstance(),
        ProductOwnerRole.getInstance(),
        CustomerAdminRole.getInstance(),
        UserManagerRole.getInstance(),
        AdminRole.getInstance()));

private Roles() {
    }

public static Collection<Role> values() {
        return values;
    }

 Other methods...
}

Every time you add a new role, you must remember to add it to the values list. It’s ugly.

The interface of the client code is still beautiful, as shown below:

<pre class=”lang-java prettyprint-override”>Collection<Role> assignables = Roles.getAssignableRolesFor(role);

All in all, your use of the enum attribute looks pretty good. Your role is constant, and the predefined values() method gives you a lot of functionality out of the box. It’s also great to have all the ranks and names in one place instead of scattered across multiple files.


The user might create a package with the same name in his client code and create a new Role, but that’s a bad practice that I wouldn’t even consider.

Related Problems and Solutions