关于枚举:是否可以在Scala中实现分层枚举?

Is there a way to implement a hierarchical enumeration in scala?

我正在一个项目中,我需要在其中标记接收到的消息的类型。消息可能来自不同的来源,但是所有这些来源都会生成具有相同概念类型(因此,具有相同含义)但以不同方式编写的消息。

例如从source1我可以收到

来源1:

1
2
3
4
5
    {
    "message_type":"typeA",
    "value": 3
     ...
    }

要么

1
2
3
4
5
    {
    "message_type":"typeB",
    "value": 3
     ...
    }

而且从source2我也可以收到

来源2:

1
2
3
4
5
   {
    "message_type":"A",
    "value": 5
     ...
   }

要么

1
2
3
4
5
    {
    "message_type":"B",
    "value": 2
     ...
    }

我想最大程度地重复使用代码,因此尝试了此解决方案。

我创建的第一个scala文件是一个特征:

1
2
3
4
trait MessageType extends Enumeration {
    val TYPE_A: Value
    val TYPE_B: Value
}

然后我在两个目标文件中实现了它:

1
2
3
4
5
6
7
object Source1MessageType extends MessageType{
    override val TYPE_A: Value("typeA")
    override val TYPE_B: Value("typeB")

object Source2MessageType extends MessageType{
    override val TYPE_A: Value("A")
    override val TYPE_B: Value("B")

所以现在我想要的是在不知道源类型的情况下检查消息的类型,如下所示:

1
2
3
4
5
6
def foo(type: MessageType.Value) {
    type match{
        case MessageType.TYPE_A => ...do A action...
        case MessageType.TYPE_B => ...do B action...
    }
}

但是,如果我编写此代码,IDE(IntelliJ)会以红色突出显示该参数,但不会提供有关该错误的信息。看来我只能使用Source1MessageType或Source2MessageType作为参数类型。

我认为错误是因为Scala不会将特征视为枚举,所以我无法访问该枚举的值。

您对此有什么解决方案吗?


是的,您可以进行层次枚举。因此,通常我建议不要使用Enumeration。这是一篇关于它为什么不好的文章

https://medium.com/@yuriigorbylov/scala-enumerations-hell-5bdba2c1216

最惯用的方法是利用类似以下特征的密封特征:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
sealed trait MessageType{
  def value:String
}
sealed trait MessageType1 extends MessageType
final case object TypeA extends MessageType1{
   override def value:String ="typeA"
}
final case object TypeB extends MessageType1{
   override def value:String ="typeB"
}
sealed trait MessageType2 extends MessageType
final case object A extends MessageType2{
   override def value:String ="A"
}
final case object B extends MessageType2{
   override def value:String ="B"
}

请注意,所有这些定义都必须位于同一文件中。现在这行得通,因为sealedfinal告诉编译器继承只能在该文件中发生。

这意味着在给定MessageType2实例的情况下,编译器知道它只能是对象AB,而不能是其他任何东西(由于密封/最终)

这使您可以在模式匹配等方面对枚举进行详尽的检查。


如果" A"和" typeA"是同一消息类型的名称,那么当您读入数据时,您需要进行处理。这意味着您在枚举中不需要任何嵌套。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
trait MessageType
case object MessageTypeA extends MessageType
case object MessageTypeB extends MessageType

object MessageType {
  def apply(s: String): MessageType =
    s match {
      case"A" |"typeA" => MessageTypeA
      case"B" |"typeB" => MessageTypeB
      case _ => throw BadMessageType
    }
}

case class Message(msgType: MessageType, value: Int)

您可以使用MessageType()创建消息类型,并将根据需要返回MessageTypeAMessageTypeB。您可以使用普通的match来确定您拥有哪种消息类型。

如果需要保留生成MessageType的原始字符串,则可以将其作为抽象值存储在MessageType特征中,并在创建适当的实例时将其填充:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
trait MessageType {
  def origin: String
}

case class MessageTypeA(origin: String) extends MessageType
case class MessageTypeB(origin: String) extends MessageType

object MessageType {
  def apply(s: String): MessageType =
    s match {
      case"A" |"typeA" => MessageTypeA(s)
      case"B" |"typeB" => MessageTypeB(s)
      case _ => throw BadMessageType
    }
}