在Scala中,何时使用案例类(或案例对象)与扩展枚举有什么最佳实践指南吗?
它们似乎提供了一些相同的好处。
在Scala中,何时使用案例类(或案例对象)与扩展枚举有什么最佳实践指南吗?
它们似乎提供了一些相同的好处。
当前回答
我在这里有一个简单的库,允许你使用密封的trait /类作为枚举值,而不必维护自己的值列表。它依赖于一个简单的宏,不依赖于有bug的knownDirectSubclasses。
https://github.com/lloydmeta/enumeratum
其他回答
在过去几次我需要这两个选择的时候,我一直在反复考虑。直到最近,我的偏好一直是密封的trait/case对象选项。
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对象/密封特征
优点: 使用密封特征,我们可以预实例化一些值,而其他值可以在创建时注入 完全支持模式匹配(应用/取消应用定义的方法)
缺点: -从持久存储实例化-你经常需要在这里使用模式匹配或者定义你自己的所有可能的“enum值”列表
最终让我改变观点的是以下片段:
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对象/密封特征。
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或谷歌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
}
漂亮而干净,具有case类和Java枚举的所有优点。就我个人而言,我在对象之外定义枚举值,以更好地匹配惯用的Scala代码:
object Currency extends Enum[Currency]
sealed trait Currency extends Currency.Value
case object EUR extends Currency
case object GBP extends Currency
一个很大的区别是枚举支持从某个名称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的主要缺点是:
不能遍历“枚举”的所有实例。这当然是事实,但我发现在实践中很少需要这样做。 不容易从持久化值实例化。这也是正确的,但是,除了在大量枚举的情况下(例如,所有货币),这并不会带来巨大的开销。
如果你想要维护与其他JVM语言(如Java)的互操作性,那么最好的选择是编写Java枚举。这些功能在Scala和Java代码中都可以透明地工作,这在Scala中是做不到的。枚举或case对象。如果可以避免的话,让我们不要为GitHub上的每个新爱好项目都创建一个新的枚举库!
我在这里有一个简单的库,允许你使用密封的trait /类作为枚举值,而不必维护自己的值列表。它依赖于一个简单的宏,不依赖于有bug的knownDirectSubclasses。
https://github.com/lloydmeta/enumeratum