在Scala中,何时使用案例类(或案例对象)与扩展枚举有什么最佳实践指南吗?

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


当前回答

我认为与枚举相比,使用case类的最大优点是可以使用类型类模式,也就是ad-hoc polymorphysm。不需要像这样匹配枚举:

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() = ...
}

其他回答

我在这里有一个简单的库,允许你使用密封的trait /类作为枚举值,而不必维护自己的值列表。它依赖于一个简单的宏,不依赖于有bug的knownDirectSubclasses。

https://github.com/lloydmeta/enumeratum

与枚举相比,使用case类的优点是:

当使用密封大小写类时,Scala编译器可以判断匹配是否完全指定,例如,当所有可能的匹配都支持在匹配声明中。对于枚举,Scala编译器无法判断。 Case类自然比支持名称和ID的基于值的枚举支持更多的字段。

使用枚举而不是case类的优点是:

枚举通常需要编写更少的代码。 对于Scala新手来说,枚举比较容易理解,因为它们在其他语言中很普遍

所以一般来说,如果你只需要一个简单的常量列表的名称,使用枚举。否则,如果你需要一些更复杂的东西,或者想要编译器告诉你是否指定了所有匹配的额外安全,用例类。

一个很大的区别是枚举支持从某个名称String实例化它们。例如:

object Currency extends Enumeration {
   val GBP = Value("GBP")
   val EUR = Value("EUR") //etc.
} 

然后你可以这样做:

val ccy = Currency.withName("EUR")

这在希望持久化枚举(例如,到数据库)或从驻留在文件中的数据创建枚举时非常有用。然而,我发现在Scala中枚举通常有点笨拙,给人一种笨拙的附加组件的感觉,所以我现在倾向于使用case对象。case对象比enum更灵活:

sealed trait Currency { def name: String }
case object EUR extends Currency { val name = "EUR" } //etc.

case class UnknownCurrency(name: String) extends Currency

所以现在我的优势是……

trade.ccy match {
  case EUR                   =>
  case UnknownCurrency(code) =>
}

正如@ chaotic3equilibrium所指出的(为了便于阅读,做了一些更正):

关于“UnknownCurrency(code)”模式,除了“破坏”currency类型的封闭集性质外,还有其他方法可以处理找不到货币代码字符串的问题。类型为Currency的UnknownCurrency现在可以潜入API的其他部分。 建议将这种情况推到枚举之外,并让客户端处理Option[Currency]类型,这将清楚地表明确实存在匹配问题,并“鼓励”API的用户自己进行排序。

为了跟进这里的其他答案,case对象相对于Enumerations的主要缺点是:

不能遍历“枚举”的所有实例。这当然是事实,但我发现在实践中很少需要这样做。 不容易从持久化值实例化。这也是正确的,但是,除了在大量枚举的情况下(例如,所有货币),这并不会带来巨大的开销。

我见过让case类模拟枚举的各种版本。以下是我的看法:

trait CaseEnumValue {
    def name:String
}

trait CaseEnum {
    type V <: CaseEnumValue
    def values:List[V]
    def unapply(name:String):Option[String] = {
        if (values.exists(_.name == name)) Some(name) else None
    }
    def unapply(value:V):String = {
        return value.name
    }
    def apply(name:String):Option[V] = {
        values.find(_.name == name)
    }
}

它允许你构造如下所示的case类:

abstract class Currency(override name:String) extends CaseEnumValue {
}

object Currency extends CaseEnum {
    type V = Site
    case object EUR extends Currency("EUR")
    case object GBP extends Currency("GBP")
    var values = List(EUR, GBP)
}

也许有人可以想出一个更好的技巧,而不是像我这样简单地向列表中添加一个each case类。这是我当时所能想到的。

如果你想要维护与其他JVM语言(如Java)的互操作性,那么最好的选择是编写Java枚举。这些功能在Scala和Java代码中都可以透明地工作,这在Scala中是做不到的。枚举或case对象。如果可以避免的话,让我们不要为GitHub上的每个新爱好项目都创建一个新的枚举库!