Scala:向枚举类型添加方法

4

我有一个简单的枚举类型,如下:

object ConditionOperator extends Enumeration {

  val Equal           = Value("equal")
  val NotEqual        = Value("notEqual")
  val GreaterOrEqual  = Value("greaterOrEqual")
  val Greater         = Value("greater")
  val LessOrEqual     = Value("lessOrEqual")
  val Less            = Value("less")

我希望您可以为每个枚举添加一个方法,这样我就可以像这样使用它:
def buildSqlCondition(field: String, operator: ConditionOperator.Value, value: String ) = {
  val sqlOperator = operator.toSql
  [...]

因此,ConditionOperator.Equal.toSql会返回"=",ConditionOperator.NotEqual.toSql会返回"<>",等等...

但我不知道如何定义一个toSql方法,以便每个枚举可以"看到"它自己的值,并决定如何将自己转换为SQL运算符...


2
可能是 https://dev59.com/3m855IYBdhLWcg3wbztl?rq=1 的重复问题。 - Richard Sitze
3个回答

7

这是我在过去搜索有关Scala 2.9.2的主题时发现的示例:

object Progress extends Enumeration {
    type enum = Value

    val READY = new ProgressVal {
      val isActive = false
      def myMethod: Any = { .. }
    }

    val EXECUTE = new ProgressVal {
      val isActive = true
      def myMethod: Any = { .. }
    }

    val COMPLETE = new ProgressVal {
      val isActive = false
      def myMethod: Any = { .. }
    }

    protected abstract class ProgressVal extends Val() {
      val isActive: Boolean
      def myMethod: Any
    }
    implicit def valueToProgress(valu: Value) = valu.asInstanceOf[ProgressVal]
}
type Progress = Progress.enum
  • 隐式类型转换是使此操作更易用的关键。

  • 枚举类型和进度类型有些冗余,但我包含它们以展示我发现的两个概念都很有帮助。


为了恰当地致谢,这个思路最初来自 Sean Ross 的回答,具体可以参见 这个问题的重复部分的回答


5
您可以通过定义一个内部类来覆盖Enumeration.Val从而开始操作。为了简化事情,我们称之为 Value (我们重载了在Enumeration中定义的Value的原始含义)。 因此,我们有了新的Value类型,它继承了Enumeration.Val,后者则继承了Enumeration.Value
鉴于toSql没有副作用,并且对每个枚举返回不同的字符串,您可能只需将其设置为 val
最后,为了使其完全可用,您需要让ConditionOperator.apply和ConditionOperator.withName返回您新定义的Value类,而不是在Enumeration中定义的Value类型。否则,在使用apply或withName按索引/名称查找ConditionOperator实例时,您将无法调用toSql,因为枚举类型不够具体。理想情况下,我们希望只覆盖applywithName并添加到ConditionOperator.Value的转换,但这些方法是final的。 但是,我们可以在这里使用一个小技巧:定义具有相同签名但附加隐式参数的新方法applywithName(Predef.DummyImplicit完美地适合此角色)。附加参数确保签名不同,以便我们能够定义这些新方法,同时与原始的apply/withName方法几乎无法区分。Scala的重载解析规则确保编译器选择我们的新方法(因此,在实践中,我们已经遮蔽了原始方法)。
object ConditionOperator extends Enumeration {
  // Here we overload the meaning of "Value" to suit our needs
  class Value(name: String, val toSql: String) extends super.Val(name) {
    def someFlag: Boolean = true // An example of another method, that you can override below
  }
  val Equal           = new Value("equal", "=")
  val NotEqual        = new Value("notEqual", "<>")
  val GreaterOrEqual  = new Value("greaterOrEqual", ">=")
  val Greater         = new Value("greater", ">")
  val LessOrEqual     = new Value("lessOrEqual", "<=") { override def someFlag = false }
  val Less            = new Value("less", "<")  
  final def apply(x: Int)( implicit dummy: DummyImplicit ): Value = super.apply(x).asInstanceOf[Value]
  final def withName(s: String)( implicit dummy: DummyImplicit ): Value = super.withName(s).asInstanceOf[Value]
}

您可以检查自己现在可以执行类似ConditionOperator(2).toSql或者ConditionOperator.withName("greaterOrEqual")的操作,两者都能按预期返回">="。 最后,以上这些繁琐的步骤可以被抽象出来:

abstract class CustomEnumeration extends Enumeration {
  type BaseValue = super.Val
  type CustomValue <: super.Value
  type Value = CustomValue
  final def apply(x: Int)( implicit dummy: DummyImplicit ): CustomValue = super.apply(x).asInstanceOf[CustomValue]
  final def withName(s: String)( implicit dummy: DummyImplicit ): CustomValue = super.withName(s).asInstanceOf[CustomValue]
}
object ConditionOperator extends CustomEnumeration {
  class CustomValue(name: String, val toSql: String) extends BaseValue(name) {
    def someFlag: Boolean = true
  }
  val Equal           = new Value("equal", "=")
  val NotEqual        = new Value("notEqual", "<>")
  val GreaterOrEqual  = new Value("greaterOrEqual", ">=")
  val Greater         = new Value("greater", ">")
  val LessOrEqual     = new Value("lessOrEqual", "<=") { override def someFlag = false }
  val Less            = new Value("less", "<")  
}

1
顺带提一下,将类型转换为“Value”的目的与Richard Sitze的解决方案完全相同。这里的区别在于转换不会泄露到外部,并且只是枚举的内部实现细节。另一方面,你可能会认为我的解决方案有点复杂,所以如果简单性胜过一切(通常来说这是一个很好的原则),你可能更愿意避免它。 - Régis Jean-Gilles
但与@Richard Sitze的解决方案相反,你的解决方案在处理此代码时失败: ConditionOperator.values.map(_.toSql)values方法返回ValueSet,因此我不确定如何修复它。 - Michael Berdyshev

1
object ConditionOperator extends Enumeration {

  implicit def overrideValue(v:Value) = new OverridedValue(v)
  class OverridedValue(val v:Value) {
    def toSql = v.toString
  }
  val Equal           = Value("=")
  val NotEqual        = Value("<>")
  val GreaterOrEqual  = Value(">=")
  val Greater         = Value(">")
  val LessOrEqual     = Value("<=")
  val Less            = Value("<")

}

import ConditionOperator._

assert(Equal.toSql == "=")

在Scala 2.10中,您可以使用隐式类使其更简单,替换


implicit def overrideValue(v:Value) = new OverridedValue(v)
class OverridedValue(val v:Value) {
  def toSql = v.toString
}

使用

implicit class OverridedValue(val v:Value) {
  def toSql = v.toString
}

我不喜欢隐式地总是在每次引用枚举值时创建一个新对象。 - Richard Sitze
我不喜欢你在回答这个琐碎问题时使用那些充满Java风格的样板代码。 - xiefei

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接