在 Scala 最佳实践中使用枚举

spa*_*rkr 1 enums scala

我一直在 Scala 中使用密封特征和 case 对象来定义枚举类型,最近我遇到了另一种在 Scala 中扩展枚举类的方法,如下所示:

object CertificateStatusEnum extends Enumeration {
  val Accepted, SignatureError, CertificateExpired, CertificateRevoked, NoCertificateAvailable, CertChainError, ContractCancelled = Value
}
Run Code Online (Sandbox Code Playgroud)

反对做这样的事情:

sealed trait CertificateStatus
object CertificateStatus extends {
  case object Accepted               extends CertificateStatus
  case object SignatureError         extends CertificateStatus
  case object CertificateExpired     extends CertificateStatus
  case object CertificateRevoked     extends CertificateStatus
  case object NoCertificateAvailable extends CertificateStatus
  case object CertChainError         extends CertificateStatus
  case object ContractCancelled      extends CertificateStatus
}
Run Code Online (Sandbox Code Playgroud)

什么被认为是好的方法?

Ali*_*iel 5

它们都是为了简单的目的而完成工作,但就最佳实践而言,sealed traits+的使用case objects更加灵活。

背后的故事是,由于 Scala 附带了 Java 所拥有的一切,因此 Java 拥有枚举,而出于互操作性的原因,Scala 必须将它们放在那里。但 Scala 不需要它们,因为它支持 ADT(代数数据类型),因此它可以像您刚刚看到的那样以函数方式生成枚举。

使用普通Enumeration课程您会遇到某些限制:

  • 编译器无法彻底检测模式匹配
  • 实际上,扩展元素以容纳除String名称和IntID 之外的更多数据实际上更困难,因为Value它是最终的。
  • 在运行时,由于类型擦除,所有枚举都具有相同的类型,因此类型级编程受到限制 - 例如,不能有重载方法。
  • 当你这样做时,object CertificateStatusEnum extends Enumeration你的枚举不会定义为CertificateStatusEnum类型,而是被定义为CertificateStatusEnum.Value- 所以你必须使用一些类型别名来解决这个问题。这样做的问题是你的同伴的类型仍然是这样CertificateStatusEnum.Value.type,所以你最终会使用多个别名来解决这个问题,并且有一个相当混乱的枚举。

另一方面,代数数据类型是一种类型安全的替代方案,您可以在其中指定每个元素的形状并对枚举进行编码,您只需要使用(或) 和精确表达的求和类型sealed traitsabstract classescase objects

这些解决了类的限制Enumeration,但是您会遇到一些其他(次要)缺点,尽管这些并不是限制:

  • case 对象没有默认顺序 - 因此,如果您需要一个默认顺序,则必须将您的id属性添加为sealed trait并提供排序方法。
  • 一个有点问题的问题是,即使case objects是可序列化的,如果您需要反序列化枚举,则没有简单的方法可以从其枚举名称反序列化案例对象。您很可能需要编写一个自定义解串器。
  • 默认情况下你不能像使用Enumeration. 但这不是一个非常常见的用例。然而,它可以很容易地实现,例如:
  object CertificateStatus extends {
    val values: Seq[CertificateStatus] = Seq(
      Accepted,
      SignatureError,
      CertificateExpired,
      CertificateRevoked,
      NoCertificateAvailable,
      CertChainError,
      ContractCancelled
    )
    // rest of the code
  }
Run Code Online (Sandbox Code Playgroud)

实际上,没有什么是你能用Enumeration+ 做不到sealed traitcase objects。于是前者就脱离了人们的喜好,偏向于后者。此比较仅涉及 Scala 2。

在 Scala 3 中,他们将 ADT 及其通用版本 (GADT) 与枚举统一在新的强大语法下,有效地为您提供所需的一切。所以你有充分的理由使用它们。正如盖尔提到的,他们成为一流的实体。