Using OO design to avoid primitive-based pattern matching

There are cases where we want to pattern match based on primitive types. For example I want to pattern match the application configuration and see if the saving mode is AWS or GCC based. In that case I could have something like:

appConfiguration.saveMode match {
  case "AWS" => ??? 
  case "GCC" => ??? 


Now, the first clean up I can do is to have two constants and use them instead, something like:

val AmazonWebServices: String = "aws"
val GoogleCloud: String = "GCC"

And then pattern match on those constants. Is there a better way to avoiding primitive pattern matching? Is there a “go to” pattern I can use when I’m in these situations?

I’d probably write a simple ADT with all cases, and then use these instead of Strings throughout the program:

sealed abstract class SaveMode(val string: String)
case object AmazonWebServices extends SaveMode("AWS")
case object GoogleCloud extends SaveMode("GCC")

You can provide a method, that converts a string to the right instance, which is best called only by the code reading in the configuration (if your current configuration storage method allows that).

object SaveMode {
  val saveModes: List[SaveMode] = List(
    AmazonWebServices, GoogleCloud

  def fromString(str:String): Option[SaveMode] =
    saveModes.find(_.string == str)

The list of modes still has to be updated when new possible values are added, but for all usages of SaveMode in pattern matches you now get exhaustiveness checks by the compiler, so you cannot miss a case.

Depending on how you decide to parse the strings to instances of SaveMode, you can of course leave off the val string:String and change SaveMode to a trait.

Thanks, your answer is useful. Good OO pattern for this purpose.