在Scala中,何时使用案例类(或案例对象)与扩展枚举有什么最佳实践指南吗?
它们似乎提供了一些相同的好处。
在Scala中,何时使用案例类(或案例对象)与扩展枚举有什么最佳实践指南吗?
它们似乎提供了一些相同的好处。
当前回答
2017年3月更新:正如Anthony Accioly所评论的,scala。枚举/枚举PR已关闭。
Dotty (Scala的下一代编译器)将占据主导地位,尽管Dotty发行于1970年,Martin Odersky的PR发行于1958年。
注意:现在(2016年8月,6年多之后)有一个移除scala的提案。编号:PR 5352
scala抨击。枚举,添加@enum注释 的语法
@enum
class Toggle {
ON
OFF
}
是一个可能的实现示例,目的是也支持符合某些限制(没有嵌套,递归或变化的构造函数参数)的adt,例如:
@enum
sealed trait Toggle
case object ON extends Toggle
case object OFF extends Toggle
Deprecates the unmitigated disaster that is scala.Enumeration. Advantages of @enum over scala.Enumeration: Actually works Java interop No erasure issues No confusing mini-DSL to learn when defining enumerations Disadvantages: None. This addresses the issue of not being able to have one codebase that supports Scala-JVM, Scala.js and Scala-Native (Java source code not supported on Scala.js/Scala-Native, Scala source code not able to define enums that are accepted by existing APIs on Scala-JVM).
其他回答
当您需要遍历或过滤所有实例时,case类相对于枚举的另一个缺点。这是枚举(以及Java枚举)的内置功能,而case类并不自动支持这种功能。
换句话说:“没有简单的方法可以获得包含case类的全部枚举值集的列表”。
我认为与枚举相比,使用case类的最大优点是可以使用类型类模式,也就是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() = ...
}
在过去几次我需要这两个选择的时候,我一直在反复考虑。直到最近,我的偏好一直是密封的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类模拟枚举的各种版本。以下是我的看法:
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)
}
也许有人可以想出一个更好的技巧,而不是像我这样简单地向列表中添加一个each case类。这是我当时所能想到的。
我更喜欢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 }
}