Case objects vs Enumerations in Scala

前端 未结 14 1167
误落风尘
误落风尘 2020-11-22 16:45

Are there any best-practice guidelines on when to use case classes (or case objects) vs extending Enumeration in Scala?

They seem to offer some of the same benefits.

14条回答
  •  独厮守ぢ
    2020-11-22 17:28

    I've been going back and forth on these two options the last few times I've needed them. Up until recently, my preference has been for the sealed trait/case object option.

    1) Scala Enumeration Declaration

    object OutboundMarketMakerEntryPointType extends Enumeration {
      type OutboundMarketMakerEntryPointType = Value
    
      val Alpha, Beta = Value
    }
    

    2) Sealed Traits + Case Objects

    sealed trait OutboundMarketMakerEntryPointType
    
    case object AlphaEntryPoint extends OutboundMarketMakerEntryPointType
    
    case object BetaEntryPoint extends OutboundMarketMakerEntryPointType
    

    While neither of these really meet all of what a java enumeration gives you, below are the pros and cons:

    Scala Enumeration

    Pros: -Functions for instantiating with option or directly assuming accurate (easier when loading from a persistent store) -Iteration over all possible values is supported

    Cons: -Compilation warning for non-exhaustive search is not supported (makes pattern matching less ideal)

    Case Objects/Sealed traits

    Pros: -Using sealed traits, we can pre-instantiate some values while others can be injected at creation time -full support for pattern matching (apply/unapply methods defined)

    Cons: -Instantiating from a persistent store - you often have to use pattern matching here or define your own list of all possible 'enum values'

    What ultimately made me change my opinion was something like the following snippet:

    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
    }
    

    The .get calls were hideous - using enumeration instead I can simply call the withName method on the enumeration as follows:

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

    So I think my preference going forward is to use Enumerations when the values are intended to be accessed from a repository and case objects/sealed traits otherwise.

提交回复
热议问题