Scala中的Case对象与枚举的比较

251

有没有最佳实践指南,说明何时在Scala中使用case classes(或case objects)与扩展Enumeration?

它们似乎提供了一些相同的好处。


2
我写了一个关于Scala枚举及其替代方案的小概述,你可能会觉得它有用:pedrorijo.com/blog/scala-enums/ - pedrorijo91
3
请参阅基于Dotty的Scala 3 enum(截至2020年年中)链接 - VonC
如果使用Scala 2.X,请使用由Typelevel管理的标准化实现:https://github.com/lloydmeta/enumeratum - chaotic3quilibrium
14个回答

3

我更喜欢使用case对象(这只是个人偏好)。为了应对这种方法固有的问题(解析字符串并迭代所有元素),我添加了一些不完美但有效的代码。

我在这里粘贴代码,希望它能有用,并且其他人也可以改进它。

/**
 * Enum for Genre. It contains the type, objects, elements set and parse method.
 *
 * This approach supports:
 *
 * - Pattern matching
 * - Parse from name
 * - Get all elements
 */
object Genre {
  sealed trait Genre

  case object MALE extends Genre
  case object FEMALE extends Genre

  val elements = Set (MALE, FEMALE) // You have to take care this set matches all objects

  def apply (code: String) =
    if (MALE.toString == code) MALE
    else if (FEMALE.toString == code) FEMALE
    else throw new IllegalArgumentException
}

/**
 * Enum usage (and tests).
 */
object GenreTest extends App {
  import Genre._

  val m1 = MALE
  val m2 = Genre ("MALE")

  assert (m1 == m2)
  assert (m1.toString == "MALE")

  val f1 = FEMALE
  val f2 = Genre ("FEMALE")

  assert (f1 == f2)
  assert (f1.toString == "FEMALE")

  try {
    Genre (null)
    assert (false)
  }
  catch {
    case e: IllegalArgumentException => assert (true)
  }

  try {
    Genre ("male")
    assert (false)
  }
  catch {
    case e: IllegalArgumentException => assert (true)
  }

  Genre.elements.foreach { println }
}

2

最近几次需要使用它们时,我一直在这两个选项之间犹豫不决。直到最近,我更喜欢使用sealed trait / case object选项。

1)Scala枚举声明

object OutboundMarketMakerEntryPointType extends Enumeration {
  type OutboundMarketMakerEntryPointType = Value

  val Alpha, Beta = Value
}

2)密封特质 + 情况对象

sealed trait OutboundMarketMakerEntryPointType

case object AlphaEntryPoint extends OutboundMarketMakerEntryPointType

case object BetaEntryPoint extends OutboundMarketMakerEntryPointType

虽然这两种方式都不能完全满足Java枚举的所有需求,以下是它们的优缺点:

Scala枚举

优点: -提供使用选项或直接假定准确值进行实例化的函数(在从持久存储加载时更容易) -支持遍历所有可能的值

缺点: -不支持非穷尽搜索的编译警告(使模式匹配不太理想)

Case对象/密封特征

优点: -使用密封特征,我们可以预先实例化一些值,而其他值可以在创建时注入 -完全支持模式匹配(定义了apply/unapply方法)

缺点: -从持久存储实例化时,通常需要在此处使用模式匹配或定义自己的所有可能的“枚举值”列表

最终让我改变看法的是以下代码片段:

object DbInstrumentQueries {
  def instrumentExtractor(tableAlias: String = "s")(rs: ResultSet): Instrument = {
    val symbol = rs.getString(tableAlias + ".name")
    val quoteCurrency = rs.getString(tableAlias + ".quote_currency")
    val fixRepresentation = rs.getString(tableAlias + ".fix_representation")
    val pointsValue = rs.getInt(tableAlias + ".points_value")
    val instrumentType = InstrumentType.fromString(rs.getString(tableAlias +".instrument_type"))
    val productType = ProductType.fromString(rs.getString(tableAlias + ".product_type"))

    Instrument(symbol, fixRepresentation, quoteCurrency, pointsValue, instrumentType, productType)
  }
}

object InstrumentType {
  def fromString(instrumentType: String): InstrumentType = Seq(CurrencyPair, Metal, CFD)
  .find(_.toString == instrumentType).get
}

object ProductType {

  def fromString(productType: String): ProductType = Seq(Commodity, Currency, Index)
  .find(_.toString == productType).get
}

.get调用方式很丑陋 - 使用枚举,我可以简单地调用枚举中的withName方法,如下所示:

object DbInstrumentQueries {
  def instrumentExtractor(tableAlias: String = "s")(rs: ResultSet): Instrument = {
    val symbol = rs.getString(tableAlias + ".name")
    val quoteCurrency = rs.getString(tableAlias + ".quote_currency")
    val fixRepresentation = rs.getString(tableAlias + ".fix_representation")
    val pointsValue = rs.getInt(tableAlias + ".points_value")
    val instrumentType = InstrumentType.withNameString(rs.getString(tableAlias + ".instrument_type"))
    val productType = ProductType.withName(rs.getString(tableAlias + ".product_type"))

    Instrument(symbol, fixRepresentation, quoteCurrency, pointsValue, instrumentType, productType)
  }
}

因此,我认为今后在值需要从存储库中访问时使用枚举,否则使用case对象/密封特征。


我可以看出第二个代码模式是可取的(摆脱了第一个代码模式中的两个辅助方法)。然而,我找到了一种方法,使您不必在这两种模式之间做出选择。我在我发布到此线程的答案中涵盖了整个领域:https://dev59.com/KHI-5IYBdhLWcg3wW28L#25923651 - chaotic3quilibrium

0

对于那些仍在寻找如何让 GatesDa的答案工作的人: 你只需要在声明后引用该对象即可实例化它:

trait Enum[A] {
  trait Value { self: A =>
    _values :+= this
  }
  private var _values = List.empty[A]
  def values = _values
}

sealed trait Currency extends Currency.Value
object Currency extends Enum[Currency] {
  case object EUR extends Currency; 
  EUR //THIS IS ONLY CHANGE
  case object GBP extends Currency; GBP //Inline looks better
}

0

我认为拥有case classesenumerations的最大优势是,你可以使用type class pattern,也就是特定多态性。不需要像匹配枚举一样:

someEnum match {
  ENUMA => makeThis()
  ENUMB => makeThat()
}

相反,你会得到类似这样的东西:

def someCode[SomeCaseClass](implicit val maker: Maker[SomeCaseClass]){
  maker.make()
}

implicit val makerA = new Maker[CaseClassA]{
  def make() = ...
}
implicit val makerB = new Maker[CaseClassB]{
  def make() = ...
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接