有没有一种方法可以在Scala中实现分层枚举?

时间:2019-02-08 09:48:19

标签: scala enums hierarchy traits enumeration

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

例如,我可以从 source1 接收

Source1:

    {
     "message_type": "typeA",
     "value": 3 
     ...
    }

    {
     "message_type": "typeB",
     "value": 3 
     ...
    }

但是我也可以从 source2 接收

Source2:

   {
     "message_type": "A", 
     "value": 5 
     ...
   }

    {
     "message_type": "B",
     "value": 2 
     ...
    }

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

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

trait MessageType extends Enumeration {
    val TYPE_A: Value
    val TYPE_B: Value
}

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

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")

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

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不会将特征视为枚举,所以我无法访问该枚举的值。

您对此有什么解决方案?

2 个答案:

答案 0 :(得分:2)

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

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

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

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,而不能是其他任何对象(由于密封/最终)

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

答案 1 :(得分:0)

如果“ A”和“ typeA”是同一消息类型的名称,则在读取数据时需要处理此问题。这意味着您在枚举中不需要任何嵌套。

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(<string>)创建消息类型,并将根据需要返回MessageTypeAMessageTypeB。您可以使用普通的match来确定您拥有哪种消息类型。


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

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
    }
}