2

I am trying to implement a Role class/interface/enum that I can use throughout my program. I want the roles to be somewhat categorized - I want some roles to be of type A, some roles to be of type B, and some roles to be part of multiple types. I want to be able to know all the roles from each type - so an enum/sealed class structure is the idea. I tried the following implementation -

sealed interface UserRole {
  val roleName: String
}

enum class RoleA(override val roleName: String): UserRole {
  A(roleName = "A"),
  B(roleName = "B"),
  C(roleName = "C"),
  D(roleName = "D");

  companion object {
    fun fromRoleName(roleName: String): RoleA? =
        values().singleOrNull { it.roleName == roleName }
  }
}

enum class RoleB(override val roleName: String, val secondParam: String): UserRole {
  A(roleName = "A", secondParam = "A"),
  E(roleName = "E", secondParam = "E"),
  F(roleName = "F", secondParam = "F");

  companion object {
    fun fromRoleName(roleName: String): RoleB? =
        values().singleOrNull { it.roleName == roleName }
  }
}

As you can see, A is part of both enums, but I would ideally want them to be the same object. Likewise I want to have the ability to create more of these enums in the future in case I need more types of roles.

Before I tried sealed interfaces, I simply had 1 big enum called UserRole that simply had all the values, and I used another class called RoleType and a simple mapping between the two to get what I wanted, but I don't think it does exactly what I want. Can anyone suggest a better way to categorize enum values?

  • What *exactly* is your use case? Your example is a little bit abstract, so it is hard to hands down recommend a definitive approach. From what I picked up from your question, it could be that you would be better off using your first approach. Just define a single `Role` enum which takes an array of Types in the constructor. – greyhairredbear Jun 27 '22 at 11:23
  • I came up with an approach to *maybe* achieve what you would like to do, so I posted it as an answer. My initial query of your exact use case is however still valid - the more you can share about that, the better people here will be able to help :) – greyhairredbear Jun 27 '22 at 11:36
  • And btw: Nice question, welcome to SO :) – greyhairredbear Jun 27 '22 at 11:37
  • Hey Pete! Thank you for your answer :) First of all please forgive me for the somewhat abstract example I gave, I am trying to have several roles defined in the system, in order to use Dropwizard's authentication features and @RolesAllowed annotation, to be able to lock certain endpoints behind different roles. Essentially I am trying to associate each incoming request with a user, and each user with a list of roles. But the problem is the role enum - it's working as intended, but I am trying to segment it a bit better, since when it's all in one enum it's a bit jumbled up, if that makes sense – Guy Schwartzberg Jun 27 '22 at 12:18
  • Thanks for the additional information! I am not familiar with the Dropwizard framework, but your use case does sound pretty common. And of course I can relate to the intention of wanting to structure code in an understandable way. – greyhairredbear Jun 27 '22 at 14:39

1 Answers1

1

You could reflect your roles in the type system by defining a sealed interface for each role type like this:

sealed interface Type1 {
    val t: Int
    fun test()
}

sealed interface Type2 {
    val s: String
}

Then your Role class could be defined as a sealed class and every class could implement your Role types as fit.

sealed class Role

class A(override val t: Int, override val s: String) : Role(), Type1, Type2 {
    override fun test() {
        print("hello")
    }
}

class B(override val s: String) : Role(), Type2

This does bring some overhead in the amount of code necessary to define each Role, so be aware of this when weighing pros and cons of each variant.

greyhairredbear
  • 624
  • 3
  • 10
  • 27
  • This is a very interesting approach and one that I have been experimenting with for the past hour, so far it seems to do what I want. Thank you Pete :) – Guy Schwartzberg Jun 27 '22 at 12:19
  • Nice, I'm glad if I could be of help. If you end up taking this approach, it'd be nice if you'd mark my answer as accepted. Otherwise, I'd be interested in what you've come up with, I always like to learn new stuff and would be interested in that ;) – greyhairredbear Jun 27 '22 at 14:41