代码之家  ›  专栏  ›  技术社区  ›  Alex Miller

scala中的case对象与枚举

  •  217
  • Alex Miller  · 技术社区  · 14 年前

    何时使用有什么最佳实践指南吗 case classes (或case对象)vs在scala中扩展枚举?

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

    14 回复  |  直到 5 年前
        1
  •  213
  •   Community Egal    7 年前

    一个很大的区别是 Enumeration 支持从一些 name 字符串。例如:

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

    然后你可以做:

    val ccy = Currency.withName("EUR")
    

    当希望持久化枚举(例如,对数据库)或从文件中的数据创建枚举时,这非常有用。但是,我发现一般情况下,枚举在scala中有点笨拙,并且有一种尴尬的附加组件的感觉,所以我现在倾向于使用 case 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) =>
    }
    

    AS @chaotic3quilibrium 指出(通过一些修正以便于阅读):

    对于“UnknownOccurrency(code)”模式,除了“中断”货币代码字符串 Currency 类型。 UnknownCurrency 类型化 货币 现在可以潜入API的其他部分。

    把那个箱子推出去是明智的。 枚举 让客户处理 Option[Currency] 类型将清楚地表明确实存在匹配问题,并“鼓励”API的用户自己对其进行排序。

    跟进其他答案,主要缺点是 case对象 越过 枚举 S是:

    1. 无法迭代“枚举”的所有实例 . 当然是这样,但我发现在实践中,这种要求非常罕见。

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

        2
  •  63
  •   Community Egal    7 年前

    更新: 一个新的 macro based solution 已经创建,远远优于下面我概述的解决方案。我强烈推荐使用这个新的 基于宏的解决方案 . And it appears plans for Dotty will make this style of enum solution part of the language. 哇喔!

    总结:
    尝试复制Java有三种基本模式 Enum 在scala项目中。三种模式中的两种;直接使用Java 枚举 scala.Enumeration ,无法启用scala的详尽模式匹配。第三个“密封特性+案例对象”,确实……但是 JVM class/object initialization complications 导致顺序索引生成不一致。

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

    细节:
    你要问的问题很一般,“……何时使用 case objects VS扩展 [scala.]Enumeration “。结果是有许多可能的答案,每个答案取决于您所拥有的特定项目需求的微妙之处。答案可以归结为三种基本模式。

    首先,让我们确保我们使用的是相同的枚举基本概念。让我们定义一个枚举,主要是根据 Enum provided as of Java 5 (1.5) :

    1. 它包含一组自然有序的命名成员
      1. 成员数量固定
      2. 成员是自然排序和显式索引的
        • 而不是根据一些Inate成员可查询条件进行排序
      3. 在所有成员的集合中,每个成员都有一个唯一的名称
    2. 所有成员都可以根据其索引轻松地进行迭代。
    3. 可以使用成员的(区分大小写)名称检索成员
      1. 如果还可以用不区分大小写的名称检索成员,那就太好了
    4. 可以用其索引检索成员
    5. 成员可以轻松、透明和高效地使用序列化
    6. 成员可以很容易地扩展以保存其他关联的单例ness数据。
    7. 超越Java的思考 枚举 ,如果能够显式地利用scala的模式匹配穷尽性检查进行枚举,那就更好了。

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

    一) 实际直接使用 爪哇 枚举 模式(在混合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的枚举,能够明确地利用斯卡拉的模式匹配穷举检查来枚举。

    对于我当前的项目,我没有在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,保存额外的关联单例ness数据并不容易扩展。


    c) 使用 scala.枚举 图案(灵感来自 this StackOverflow answer ):

    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的枚举,能够明确地利用斯卡拉的模式匹配穷举检查来枚举。

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


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

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

    这些解决方案中的每一个最终都可以被改写/扩展/重构,以试图覆盖每一个解决方案中缺少的一些需求。然而,Java 枚举 也不 scala.枚举 可充分扩展解决方案以提供第7项。对于我自己的项目,这是在scala中使用封闭类型的一个更令人信服的值。我强烈希望编译时警告/错误表明我的代码中存在缺口/问题,而不是必须从生产运行时异常/失败中收集它。


    在这方面,我开始与 case object 看看我是否能产生一个解决方案,涵盖上面所有的枚举定义。第一个挑战是突破JVM类/对象初始化问题的核心(在 this StackOverflow post )我终于找到了解决办法。

    因为我的解决方案有两个特点; 枚举 枚举修饰 以及自从 Enumeration 特点是超过400行长(许多评论解释上下文),我放弃粘贴到这个线程(这将使它向下伸展的页面一致)。有关详细信息,请直接跳到 主旨 .

    下面是解决方案使用与上面相同的数据思想的结果(完全注释的版本 available here )并在 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
    }
    

    这是我创建的一对新的枚举特性(位于 this Gist )实现枚举定义中所需和概述的所有功能。

    表达的一个问题是枚举成员名必须重复( decorationOrderedSet 在上面的例子中)。虽然我把它减到了一个单一的重复,但由于两个问题,我看不出如何减少它:

    1. 未定义此特定对象/案例对象模型的JVM对象/类初始化(请参见 this Stackoverflow thread )
    2. 方法返回的内容 getClass.getDeclaredClasses 有一个未定义的顺序(它不太可能与 实例对象 源代码中的声明)

    考虑到这两个问题,我不得不放弃生成一个隐含的排序的尝试,并且必须显式地要求客户机用某种有序集概念定义和声明它。因为scala集合没有插入顺序集实现,所以我能做的最好的就是使用 List 然后运行时检查它是否真的是一个集合。这不是我想要的实现这一目标的方式。

    考虑到设计需要第二个列表/集合排序 val ,鉴于 ChessPiecesEnhancedDecorated 上面的例子,可以添加 case object PAWN2 extends Member 然后忘记添加 Decoration(PAWN2,'P2', 2) 装饰顺序集 . 因此,有一个运行时检查来验证列表不仅是一个集合,而且包含扩展 sealed trait Member . 这是一种特殊的反思/宏观地狱。


    请在 主旨 .

        3
  •  62
  •   GatesDA    13 年前

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

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

    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反射的东西预先加载枚举值。非Lazy Case对象为您提供最清晰的语法:

    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
    }
    

    美观整洁,具有CASE类和Java枚举的所有优点。我个人定义对象外部的枚举值,以便更好地匹配惯用的scala代码:

    object Currency extends Enum[Currency]
    sealed trait Currency extends Currency.Value
    case object EUR extends Currency
    case object GBP extends Currency
    
        4
  •  26
  •   Aaron    14 年前

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

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

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

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

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

        5
  •  15
  •   james    7 年前

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


    如果你想要

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

    那么,下面的内容可能是有意义的。欢迎反馈。

    在这个实现中,有抽象枚举和枚举基类,您可以扩展它们。稍后我们将看到这些类,但首先,您将如何定义枚举:

    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或单个case对象中。

    下面是使用这种枚举的方法:

    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”,其中您不需要此类警告,或者在结尾只包含一个catch all case。

    当您运行上述程序时,会得到以下输出:

    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
    

    注意,由于列表和映射是不可变的,您可以轻松地删除元素以创建子集,而不必破坏枚举本身。

    以下是枚举类本身(以及其中的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)
    }
    
        6
  •  11
  •   lloydmeta    9 年前

    我这里有一个很好的简单lib,它允许您使用密封的特性/类作为枚举值,而不必维护自己的值列表。它依赖于一个简单的宏,该宏不依赖于错误 knownDirectSubclasses .

    https://github.com/lloydmeta/enumeratum

        7
  •  10
  •   Community Egal    7 年前

    更新日期:2017年3月 Anthony Accioly , the scala.Enumeration/enum 公关部已经关闭。

    Dotty 但是(下一代scala编译器)将占据主导地位 dotty issue 1970 Martin Odersky's PR 1958 .


    注:目前(2016年8月,6年后)有一项提议取消 scala.Enumeration : PR 5352

    反预测 scala.枚举 ,添加 @enum 注释

    句法

    @enum
     class Toggle {
      ON
      OFF
     }
    

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

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

    贬低了无法缓解的灾难 scala.枚举 .

    @enum优于scala.enumeration的优点:

    • 实际作品
    • 爪哇互操作
    • 无擦除问题
    • 定义枚举时不需要学习混淆的微型DSL

    缺点:没有。

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

        8
  •  8
  •   Jacek Laskowski    9 年前

    当需要遍历或过滤所有实例时,case类与枚举的另一个缺点是。这是枚举的内置能力(以及Java枚举),而CASE类不自动支持这种能力。

    换言之:“没有一种简单的方法可以获得具有case类的枚举值的总集合的列表”。

        9
  •  5
  •   Connor Doyle    10 年前

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

        10
  •  4
  •   jho    13 年前

    我已经看到了使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)
    }
    

    也许有人能想出一个更好的诀窍,而不是像我一样简单地将每个case类添加到列表中。这就是我当时能想到的全部。

        11
  •  2
  •   Mad Dog    11 年前

    最近几次我需要这两个选项时,我一直在反复讨论。直到最近,我还是倾向于使用密封特性/案例对象选项。

    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枚举

    赞成的意见: -用于用选项实例化或直接假定准确的函数(从持久存储加载时更容易) -支持对所有可能值进行迭代

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

    外壳对象/密封特性

    赞成的意见: -使用密封特性,我们可以预先实例化一些值,而其他值可以在创建时注入。 -完全支持模式匹配(定义了应用/不应用方法)

    欺骗: -从持久存储进行实例化-您通常必须在这里使用模式匹配或定义自己的所有可能“枚举值”列表。

    最终促使我改变观点的是以下几点:

    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对象/密封特性。

        12
  •  2
  •   jaguililla    9 年前

    我更喜欢 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 }
    }
    
        13
  •  0
  •   Community Egal    7 年前

    对于那些仍在寻找如何获得 GatesDa's answer to work : 您只需在声明case对象以实例化它之后引用它:

    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
    }
    
        14
  •  0
  •   Murat Mustafin    5 年前

    我认为拥有 case classes 结束 enumerations 你能用吗 type class pattern A.K.A.公司 特设多形 . 不需要匹配以下枚举:

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