在Scala中,何时使用案例类(或案例对象)与扩展枚举有什么最佳实践指南吗?
它们似乎提供了一些相同的好处。
在Scala中,何时使用案例类(或案例对象)与扩展枚举有什么最佳实践指南吗?
它们似乎提供了一些相同的好处。
当前回答
我见过让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类。这是我当时所能想到的。
其他回答
如果你想要维护与其他JVM语言(如Java)的互操作性,那么最好的选择是编写Java枚举。这些功能在Scala和Java代码中都可以透明地工作,这在Scala中是做不到的。枚举或case对象。如果可以避免的话,让我们不要为GitHub上的每个新爱好项目都创建一个新的枚举库!
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
我认为与枚举相比,使用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 /类作为枚举值,而不必维护自己的值列表。它依赖于一个简单的宏,不依赖于有bug的knownDirectSubclasses。
https://github.com/lloydmeta/enumeratum
我更喜欢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 }
}