247

是否有关于何时使用案例类(或案例对象)与在 Scala 中扩展枚举的最佳实践指南?

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

4

14 回答 14

235

一个很大的区别是Enumerations 支持从一些nameString 实例化它们。例如:

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

然后你可以这样做:

val ccy = Currency.withName("EUR")

当希望持久化枚举(例如,到数据库)或从文件中的数据创建枚举时,这很有用。但是,我发现枚举在 Scala 中总体上有点笨拙,并且给人一种笨拙的附加组件的感觉,所以我现在倾向于使用case objects. Acase object比枚举更灵活:

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

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

关于“UnknownCurrency(code)”模式,除了“打破”类型的封闭集性质之外,还有其他方法可以处理找不到货币代码字符串CurrencyUnknownCurrency现在Currency可以潜入 API 的其他部分。

建议将这种情况推到外面Enumeration,让客户处理一种Option[Currency]明确表明确实存在匹配问题的类型,并“鼓励”API 的用户自己解决问题。

为了跟进这里的其他答案,case objects over Enumerations 的主要缺点是:

  1. 无法遍历 "enumeration" 的所有实例。情况确实如此,但我发现在实践中很少需要这样做。

  2. 无法从持久值轻松实例化。这也是正确的,但除了大量枚举(例如,所有货币)的情况外,这不会产生巨大的开销。

于 2009-12-14T09:27:49.393 回答
73

更新:已经创建了 一个新的基于宏的解决方案,它远远优于我在下面概述的解决方案。我强烈建议使用这个新的基于宏的解决方案Dotty 的计划似乎将使这种枚举解决方案风格成为语言的一部分。哇哦!

简介:
尝试Enum在 Scala 项目中重现 Java 有三种基本模式。三种模式中的两种;直接使用 JavaEnumscala.Enumeration, 无法启用 Scala 的详尽模式匹配。第三个;“密封特征+案例对象”,确实......但有JVM类/对象初始化复杂性导致不一致的序数索引生成。

我创建了一个包含两个类的解决方案;EnumerationEnumerationDecorated,位于此Gist中。我没有将代码发布到这个线程中,因为 Enumeration 的文件非常大(+400 行 - 包含许多解释实现上下文的注释)。

细节:
你问的问题很笼统;“......何时使用caseobjects与扩展[scala.]Enumeration”。事实证明,有很多可能的答案,每个答案都取决于您所拥有的特定项目要求的微妙之处。答案可以简化为三种基本模式。

首先,让我们确保我们的工作与枚举的基本概念相同。Enum让我们主要根据Java 5 (1.5) 提供的枚举来定义一个枚举:

  1. 它包含一组自然有序的封闭命名成员
    1. 成员数量固定
    2. 成员自然排序并显式索引
      • 与根据一些不成熟的成员可查询标准进行排序相反
    3. 每个成员在所有成员的总集合中都有一个唯一的名称
  2. 所有成员都可以根据他们的索引轻松迭代
  3. 可以使用其(区分大小写的)名称检索成员
    1. 如果成员也可以使用不区分大小写的名称来检索,那就太好了
  4. 可以使用其索引检索成员
  5. 成员可以轻松、透明和高效地使用序列化
  6. 成员可以很容易地扩展以保存其他相关的单例数据
  7. 超越 Java 的思考Enum,如果能够显式地利用 Scala 的模式匹配穷举检查来进行枚举,那就太好了

接下来,让我们看一下发布的三种最常见的解决方案模式的简化版本:

A)实际上直接使用JavaEnum模式(在混合 Scala/Java 项目中):

public enum ChessPiece {
    KING('K', 0)
  , QUEEN('Q', 9)
  , BISHOP('B', 3)
  , KNIGHT('N', 3)
  , ROOK('R', 5)
  , PAWN('P', 1)
  ;

  private char character;
  private int pointValue;

  private ChessPiece(char character, int pointValue) {
    this.character = character; 
    this.pointValue = pointValue;   
  }

  public int getCharacter() {
    return character;
  }

  public int getPointValue() {
    return pointValue;
  }
}

枚举定义中的以下项目不可用:

  1. 3.1 - 如果一个成员也可以用其不区分大小写的名称来检索,那就太好了
  2. 7 - 超越 Java 的 Enum 思考,如果能够显式地利用 Scala 的模式匹配穷举检查来进行枚举,那就太好了

对于我目前的项目,我没有在 Scala/Java 混合项目路径上冒险的好处。即使我可以选择做一个混合项目,如果/当我添加/删除枚举成员,或者正在编写一些新代码来处理现有枚举成员时,第 7 项对于让我捕捉编译时问题至关重要。


B)使用“ sealed trait+case objects ”模式:

sealed trait ChessPiece {def character: Char; def pointValue: Int}
object ChessPiece {
  case object KING extends ChessPiece {val character = 'K'; val pointValue = 0}
  case object QUEEN extends ChessPiece {val character = 'Q'; val pointValue = 9}
  case object BISHOP extends ChessPiece {val character = 'B'; val pointValue = 3}
  case object KNIGHT extends ChessPiece {val character = 'N'; val pointValue = 3}
  case object ROOK extends ChessPiece {val character = 'R'; val pointValue = 5}
  case object PAWN extends ChessPiece {val character = 'P'; val pointValue = 1}
}

枚举定义中的以下项目不可用:

  1. 1.2 - 成员自然排序并显式索引
  2. 2 - 所有成员都可以根据他们的索引轻松迭代
  3. 3 - 可以使用其(区分大小写)名称检索成员
  4. 3.1 - 如果一个成员也可以用其不区分大小写的名称来检索,那就太好了
  5. 4 - 可以使用其索引检索成员

可以说它确实符合枚举定义项 5 和 6。对于 5,声称它是有效的有点牵强。对于 6,扩展以保存其他关联的单例数据并不容易。


C)使用模式(受此StackOverflow 答案scala.Enumeration的启发):

object ChessPiece extends Enumeration {
  val KING = ChessPieceVal('K', 0)
  val QUEEN = ChessPieceVal('Q', 9)
  val BISHOP = ChessPieceVal('B', 3)
  val KNIGHT = ChessPieceVal('N', 3)
  val ROOK = ChessPieceVal('R', 5)
  val PAWN = ChessPieceVal('P', 1)
  protected case class ChessPieceVal(character: Char, pointValue: Int) extends super.Val()
  implicit def convert(value: Value) = value.asInstanceOf[ChessPieceVal]
}

枚举定义中的以下项目不可用(可能与直接使用 Java 枚举的列表相同):

  1. 3.1 - 如果一个成员也可以用其不区分大小写的名称来检索,那就太好了
  2. 7 - 超越 Java 的 Enum 思考,如果能够显式地利用 Scala 的模式匹配穷举检查来进行枚举,那就太好了

同样对于我当前的项目,如果/当我添加/删除枚举成员或正在编写一些新代码来处理现有枚举成员时,第 7 项对于让我捕捉编译时问题至关重要。


因此,鉴于上述枚举定义,上述三种解决方案均不起作用,因为它们没有提供上述枚举定义中概述的所有内容:

  1. 直接在混合 Scala/Java 项目中的 Java 枚举
  2. “密封特征+案例对象”
  3. scala.枚举

这些解决方案中的每一个最终都可以重新设计/扩展/重构,以尝试覆盖每个解决方案的一些缺失要求。但是,JavaEnumscala.Enumeration解决方案都不能充分扩展以提供第 7 项。对于我自己的项目,这是在 Scala 中使用封闭类型的更引人注目的价值之一。我非常喜欢编译时警告/错误来表明我的代码中存在差距/问题,而不是必须从生产运行时异常/故障中收集它。


在这方面,我开始使用case object路径,看看我是否可以产生一个涵盖上述所有枚举定义的解决方案。第一个挑战是推动 JVM 类/对象初始化问题的核心(在这篇 StackOverflow 帖子中有详细介绍)。我终于能够想出一个解决方案。

因为我的解决方案是两个特征;EnumerationEnumerationDecorated,并且由于Enumerationtrait 超过 400 行长(很多解释上下文的评论),我放弃将它粘贴到这个线程中(这会使其在页面上相当长)。详情请直接跳至Gist

这是使用与上述相同的数据想法(此处提供完整注释版本)并在EnumerationDecorated.

import scala.reflect.runtime.universe.{TypeTag,typeTag}
import org.public_domain.scala.utils.EnumerationDecorated

object ChessPiecesEnhancedDecorated extends EnumerationDecorated {
  case object KING extends Member
  case object QUEEN extends Member
  case object BISHOP extends Member
  case object KNIGHT extends Member
  case object ROOK extends Member
  case object PAWN extends Member

  val decorationOrderedSet: List[Decoration] =
    List(
        Decoration(KING,   'K', 0)
      , Decoration(QUEEN,  'Q', 9)
      , Decoration(BISHOP, 'B', 3)
      , Decoration(KNIGHT, 'N', 3)
      , Decoration(ROOK,   'R', 5)
      , Decoration(PAWN,   'P', 1)
    )

  final case class Decoration private[ChessPiecesEnhancedDecorated] (member: Member, char: Char, pointValue: Int) extends DecorationBase {
    val description: String = member.name.toLowerCase.capitalize
  }
  override def typeTagMember: TypeTag[_] = typeTag[Member]
  sealed trait Member extends MemberDecorated
}

这是我创建的一对新枚举特征(位于此 Gist中)的示例用法,用于实现枚举定义中所需和概述的所有功能。

表达的一种担忧是枚举成员名称必须重复(decorationOrderedSet在上面的示例中)。虽然我确实将其最小化为一次重复,但由于两个问题,我不知道如何使其更少:

  1. 此特定对象/案例对象模型的 JVM 对象/类初始化未定义(请参阅此 Stackoverflow 线程
  2. 从方法返回的内容getClass.getDeclaredClasses具有未定义的顺序(并且不太可能与case object源代码中的声明顺序相同)

鉴于这两个问题,我不得不放弃尝试生成隐含排序,而必须明确要求客户端使用某种有序集合概念定义和声明它。由于 Scala 集合没有插入有序集合实现,我能做的最好的就是使用 aList然后运行时检查它是否真的是一个集合。这不是我希望实现这一目标的方式。

鉴于设计需要第二个列表/集合排序val,鉴于ChessPiecesEnhancedDecorated上面的示例,可以添加case object PAWN2 extends Member然后忘记添加Decoration(PAWN2,'P2', 2)decorationOrderedSet。因此,有一个运行时检查来验证列表不仅是一个集合,而且包含所有扩展sealed trait Member. 那是一种特殊形式的反射/宏观地狱。请在Gist


上留下评论和/或反馈。

于 2014-09-18T22:47:32.207 回答
62

Case 对象已经为其 toString 方法返回了它们的名称,因此没有必要单独传递它。这是一个类似于 jho 的版本(为简洁起见,省略了方便的方法):

trait Enum[A] {
  trait Value { self: A => }
  val values: List[A]
}

sealed trait Currency extends Currency.Value
object Currency extends Enum[Currency] {
  case object EUR extends Currency
  case object GBP extends Currency
  val values = List(EUR, GBP)
}

对象是惰性的;通过使用 vals 我们可以删除列表,但必须重复名称:

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

sealed abstract class Currency(val name: String) extends Currency.Value
object Currency extends Enum[Currency] {
  val EUR = new Currency("EUR") {}
  val GBP = new Currency("GBP") {}
}

如果您不介意作弊,您可以使用反射 API 或 Google Reflections 之类的工具预加载枚举值。非惰性案例对象为您提供最简洁的语法:

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
  case object GBP extends Currency
}

漂亮而干净,具有案例类和 Java 枚举的所有优点。就个人而言,我在对象之外定义枚举值以更好地匹配惯用的 Scala 代码:

object Currency extends Enum[Currency]
sealed trait Currency extends Currency.Value
case object EUR extends Currency
case object GBP extends Currency
于 2011-02-10T15:18:23.553 回答
30

使用案例类优于枚举的优点是:

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

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

  • 枚举通常会少写一些代码。
  • 枚举对于刚接触 Scala 的人来说更容易理解,因为它们在其他语言中很流行

因此,一般来说,如果您只需要按名称列出的简单常量列表,请使用枚举。否则,如果您需要一些更复杂的东西或希望编译器的额外安全性告诉您是否指定了所有匹配项,请使用用例类。

于 2009-12-14T08:01:18.697 回答
15

更新:下面的代码有一个错误,在此处描述。下面的测试程序有效,但如果您在 DayOfWeek 本身之前使用 DayOfWeek.Mon(例如),它将失败,因为 DayOfWeek 尚未初始化(使用内部对象不会导致外部对象被初始化)。如果您val enums = Seq( DayOfWeek )在主类中执行类似的操作,强制初始化枚举,您仍然可以使用此代码,或者您可以使用 chaotic3quilibrium 的修改。期待基于宏的枚举!


如果你想

  • 关于非详尽模式匹配的警告
  • 分配给每个枚举值的 Int ID,您可以选择控制它
  • 枚举值的不可变列表,按定义的顺序排列
  • 从名称到枚举值的不可变映射
  • 从 id 到枚举值的不可变 Map
  • 为所有或特定枚举值或整个枚举粘贴方法/数据的地方
  • 有序枚举值(因此您可以测试,例如,是否天 < 星期三)
  • 扩展一个枚举以创建其他枚举的能力

那么以下内容可能会引起您的兴趣。欢迎反馈。

在此实现中,您可以扩展抽象 Enum 和 EnumVal 基类。我们将在一分钟内看到这些类,但首先,这是定义枚举的方式:

object DayOfWeek extends Enum {
  sealed abstract class Val extends EnumVal
  case object Mon extends Val; Mon()
  case object Tue extends Val; Tue()
  case object Wed extends Val; Wed()
  case object Thu extends Val; Thu()
  case object Fri extends Val; Fri()
  case object Sat extends Val; Sat()
  case object Sun extends Val; Sun()
}

请注意,您必须使用每个枚举值(调用其 apply 方法)才能将其变为现实。[我希望内在对象不要懒惰,除非我特别要求它们如此。我认为。]

如果需要,我们当然可以将方法/数据添加到 DayOfWeek、Val 或单个案例对象。

以下是您将如何使用这样的枚举:

object DayOfWeekTest extends App {

  // To get a map from Int id to enum:
  println( DayOfWeek.valuesById )

  // To get a map from String name to enum:
  println( DayOfWeek.valuesByName )

  // To iterate through a list of the enum values in definition order,
  // which can be made different from ID order, and get their IDs and names:
  DayOfWeek.values foreach { v => println( v.id + " = " + v ) }

  // To sort by ID or name:
  println( DayOfWeek.values.sorted mkString ", " )
  println( DayOfWeek.values.sortBy(_.toString) mkString ", " )

  // To look up enum values by name:
  println( DayOfWeek("Tue") ) // Some[DayOfWeek.Val]
  println( DayOfWeek("Xyz") ) // None

  // To look up enum values by id:
  println( DayOfWeek(3) )         // Some[DayOfWeek.Val]
  println( DayOfWeek(9) )         // None

  import DayOfWeek._

  // To compare enums as ordinals:
  println( Tue < Fri )

  // Warnings about non-exhaustive pattern matches:
  def aufDeutsch( day: DayOfWeek.Val ) = day match {
    case Mon => "Montag"
    case Tue => "Dienstag"
    case Wed => "Mittwoch"
    case Thu => "Donnerstag"
    case Fri => "Freitag"
 // Commenting these out causes compiler warning: "match is not exhaustive!"
 // case Sat => "Samstag"
 // case Sun => "Sonntag"
  }

}

这是你编译它时得到的:

DayOfWeekTest.scala:31: warning: match is not exhaustive!
missing combination            Sat
missing combination            Sun

  def aufDeutsch( day: DayOfWeek.Val ) = day match {
                                         ^
one warning found

您可以在不希望出现此类警告的情况下将“day match”替换为“(day: @unchecked) match”,或者在最后包含一个包罗万象的案例。

当你运行上面的程序时,你会得到这个输出:

Map(0 -> Mon, 5 -> Sat, 1 -> Tue, 6 -> Sun, 2 -> Wed, 3 -> Thu, 4 -> Fri)
Map(Thu -> Thu, Sat -> Sat, Tue -> Tue, Sun -> Sun, Mon -> Mon, Wed -> Wed, Fri -> Fri)
0 = Mon
1 = Tue
2 = Wed
3 = Thu
4 = Fri
5 = Sat
6 = Sun
Mon, Tue, Wed, Thu, Fri, Sat, Sun
Fri, Mon, Sat, Sun, Thu, Tue, Wed
Some(Tue)
None
Some(Thu)
None
true

请注意,由于 List 和 Maps 是不可变的,您可以轻松删除元素以创建子集,而不会破坏枚举本身。

这是 Enum 类本身(以及其中的 EnumVal):

abstract class Enum {

  type Val <: EnumVal

  protected var nextId: Int = 0

  private var values_       =       List[Val]()
  private var valuesById_   = Map[Int   ,Val]()
  private var valuesByName_ = Map[String,Val]()

  def values       = values_
  def valuesById   = valuesById_
  def valuesByName = valuesByName_

  def apply( id  : Int    ) = valuesById  .get(id  )  // Some|None
  def apply( name: String ) = valuesByName.get(name)  // Some|None

  // Base class for enum values; it registers the value with the Enum.
  protected abstract class EnumVal extends Ordered[Val] {
    val theVal = this.asInstanceOf[Val]  // only extend EnumVal to Val
    val id = nextId
    def bumpId { nextId += 1 }
    def compare( that:Val ) = this.id - that.id
    def apply() {
      if ( valuesById_.get(id) != None )
        throw new Exception( "cannot init " + this + " enum value twice" )
      bumpId
      values_ ++= List(theVal)
      valuesById_   += ( id       -> theVal )
      valuesByName_ += ( toString -> theVal )
    }
  }

}

这是它的更高级用法,它控制 ID 并将数据/方法添加到 Val 抽象和枚举本身:

object DayOfWeek extends Enum {

  sealed abstract class Val( val isWeekday:Boolean = true ) extends EnumVal {
    def isWeekend = !isWeekday
    val abbrev = toString take 3
  }
  case object    Monday extends Val;    Monday()
  case object   Tuesday extends Val;   Tuesday()
  case object Wednesday extends Val; Wednesday()
  case object  Thursday extends Val;  Thursday()
  case object    Friday extends Val;    Friday()
  nextId = -2
  case object  Saturday extends Val(false); Saturday()
  case object    Sunday extends Val(false);   Sunday()

  val (weekDays,weekendDays) = values partition (_.isWeekday)
}
于 2011-12-23T19:54:14.860 回答
13

我在这里有一个很好的简单库,它允许您使用密封的特征/类作为枚举值,而无需维护自己的值列表。它依赖于一个不依赖于 buggy 的简单宏knownDirectSubclasses

https://github.com/lloydmeta/enumeratum

于 2014-12-12T10:38:00.967 回答
10

2017 年 3 月更新:正如Anthony Accioly所说,scala.Enumeration/enumPR 已关闭。

Dotty(Scala 的下一代编译器)将带头,尽管dotty issue 1970Martin Odersky 的 PR 1958


注意:现在(2016 年 8 月,6 年多后)有删除提案scala.EnumerationPR 5352

弃用scala.Enumeration,添加@enum注释

语法

@enum
 class Toggle {
  ON
  OFF
 }

是一个可能的实现示例,目的是还支持符合某些限制(无嵌套、递归或可变构造函数参数)的 ADT,例如:

@enum
sealed trait Toggle
case object ON  extends Toggle
case object OFF extends Toggle

弃用scala.Enumeration.

@enum 相对于 scala.Enumeration 的优势:

  • 实际有效
  • Java 互操作
  • 没有擦除问题
  • 定义枚举时无需学习令人困惑的 mini-DSL

缺点:没有。

这解决了无法拥有一个支持 Scala-JVMScala.js和 Scala-Native 的代码库的问题(Java 源代码不支持Scala.js/Scala-Native,Scala 源代码无法定义 Scala-JVM 上现有 API 接受的枚举)。

于 2016-08-19T12:45:02.117 回答
8

当您需要迭代或过滤所有实例时,案例类与枚举的另一个缺点。这是枚举(以及 Java 枚举)的内置功能,而案例类不会自动支持这种功能。

换句话说:“没有简单的方法来获得带有案例类的枚举值的总集列表”。

于 2009-12-14T15:23:26.227 回答
6

如果您认真维护与其他 JVM 语言(例如 Java)的互操作性,那么最好的选择是编写 Java 枚举。这些从 Scala 和 Java 代码中透明地工作,这不仅仅是针对scala.Enumeration或案例对象的说法。如果可以避免的话,我们不要为 GitHub 上的每个新爱好项目都创建一个新的枚举库!

于 2013-11-21T01:08:44.993 回答
4

我见过使案例类模仿枚举的各种版本。这是我的版本:

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

它允许您构造如下所示的案例类:

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

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

于 2010-12-27T20:22:51.343 回答
3

我更喜欢case objects(这是个人喜好问题)。为了解决该方法固有的问题(解析字符串并遍历所有元素),我添加了一些不完美但有效的行。

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

/**
 * 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 }
}
于 2015-04-06T22:25:44.720 回答
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 枚举给你的所有东西,但以下是优缺点:

斯卡拉枚举

优点: - 使用选项实例化或直接假设准确的功能(从持久存储加载时更容易) - 支持对所有可能值的迭代

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

案例对象/密封特征

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

缺点: - 从持久存储中实例化 - 您经常必须在此处使用模式匹配或定义您自己的所有可能的“枚举值”列表

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

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

所以我认为我的偏好是在打算从存储库访问值时使用枚举,否则使用案例对象/密封特征。

于 2012-11-29T18:04:11.433 回答
0

我认为 over 的最大优势case classesenumerations您可以使用type class pattern aka 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() = ...
}
于 2018-12-05T12:00:15.500 回答
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
}
于 2016-05-06T15:42:08.637 回答