有没有最佳实践指南,说明何时在Scala中使用case classes(或case objects)与扩展Enumeration?
它们似乎提供了一些相同的好处。
我更喜欢使用case对象
(这只是个人偏好)。为了应对这种方法固有的问题(解析字符串并迭代所有元素),我添加了一些不完美但有效的代码。
我在这里粘贴代码,希望它能有用,并且其他人也可以改进它。
/**
* 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 }
}
最近几次需要使用它们时,我一直在这两个选项之间犹豫不决。直到最近,我更喜欢使用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枚举的所有需求,以下是它们的优缺点:
Scala枚举
优点: -提供使用选项或直接假定准确值进行实例化的函数(在从持久存储加载时更容易) -支持遍历所有可能的值
缺点: -不支持非穷尽搜索的编译警告(使模式匹配不太理想)
Case对象/密封特征
优点: -使用密封特征,我们可以预先实例化一些值,而其他值可以在创建时注入 -完全支持模式匹配(定义了apply/unapply方法)
缺点: -从持久存储实例化时,通常需要在此处使用模式匹配或定义自己的所有可能的“枚举值”列表
最终让我改变看法的是以下代码片段:
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对象/密封特征。
对于那些仍在寻找如何让 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
}
我认为拥有case classes
比enumerations
的最大优势是,你可以使用type class pattern,也就是特定多态性。不需要像匹配枚举一样:
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() = ...
}
enum
(截至2020年年中)链接。 - VonC