Scala中使用null的可选参数的模式

时间:2013-12-29 11:45:55

标签: scala coding-style null options optional-parameters

我有一个带可选参数的函数。但是,这些不是选项[?],但可以设置为null:

private def div(id: String = null, cssClass: String = null): JQuery = {
  val optId = Option(id)
  val optCssClass = Option(cssClass)
  ...
  // deal with optId and optCssClass using the Scala-way™
  ...
}

我正在使用“null”,我知道应该像瘟疫那样避免使用“null”。但是,它允许我编写这样的代码:

div(id = "someId") // no cssClass
div(id = "otherId", cssClass = "someClass")
我的眼睛看起来比以下更好:

div(id = Some("someId")) // no cssClass
div(id = Some("otherId"), cssClass = Some("someClass"))

这是一个已知/可接受的Scala模式吗? (使用null作为默认参数值并转换为Option)

或者它仍然是痛苦/糟糕的做法?如果是这样,为什么?

4 个答案:

答案 0 :(得分:0)

为什么不用空字符串替换null

private def div(id: String = "", cssClass: String = ""): JQuery = {
  val optId = if(id.isEmpty) None else Some(id)
  val optCssClass = if(cssClass.isEmpty) None else Some(cssClass)
  ...
  // deal with optId and optCssClass using the Scala-way™
  ...
}

然后你可以这样做:

div(id = "someId") // no cssClass
div(id = "otherId", cssClass = "someClass")

答案 1 :(得分:0)

我建议的另一种方法是构建器模式

trait ElementBuilder {
  def identified(id: String): ElementBuilder
  def build: JQuery
}

case class DivElement(identifier: Option[String] = None) 
  extends ElementBuilder {
  def identified(id: String) = this.copy(identifier = Option(id))
  def build: JQuery = ??? // Smth like <div id={identifier}></div>
}

val builder = DivElement()
builder.identified("foo")
val element = builder.build

此方法允许您显式设置参数,然后通过它们构建元素

答案 2 :(得分:0)

这里的大多数答案都提出了“空对象”模式的一些变体,通过将空字符串表示为“未定义”(如val optId = if(id.isEmpty) None else Some(id)

这里的问题是空字符串可能是有效值!对于任何字符串都是如此,尽管您可以通过使用非常令人发指的东西(可能涉及不可打印的字符)来缓解此问题。 e.g:

val UndefinedString = "THIS-IS-A-REALLY-UNLIKELY-VALID-VALUE"

private def div(
  id: String = UndefinedString,
  cssClass: String = UndefinedString
): JQuery = {
  val optId = Option(id) filter (_ != UndefinedString )
  val optCssClass = Option(cssClass) filter (_ != UndefinedString )
  ...
  // deal with optId and optCssClass using the Scala-way™
  ...
}

更好的是,您可以使用其他类型来表示您的null对象。由于你不能继承String,你必须将你的参数提升到类型层次结构并使它们成为CharSequence s

object NullCharSeq extends CharSequence {
  def charAt(idx: Int): Char = ???
  def length(): Int = 0
  def subSequence(start: Int, end: Int): CharSequence = this
  def toString(): String = ???
}

def charSeqToOptStr(cs: CharSequence): Option[String] = cs match {
  case NullCharSeq => None
  case x => Option(x) map (_.toString)
}

private def div(
  id: CharSequence = NullCharSeq,
  cssClass: CharSequence = NullCharSeq
): JQuery = {
  val optId = charSeqToOptStr(id)
  val optCssClass = charSeqToOptStr(cssClass)
  ...
  // deal with optId and optCssClass using the Scala-way™
  ...
}

这是一次性使用的重量级模式,但如果你经常使用它,成本会很快摊销(NullCharSeqcharSeqToOptStr只需要在代码库中定义一次)。

错误地传递“未定义”字符串也没有风险,就像它是一个有效值一样。另外,您可以直接接受CharBuffer / StringBuffer / StringBuilder作为参数。

答案 3 :(得分:-1)

如果这是一个明智的价值,我也会在@ Jiafeng的答案中找到一个像空字符串一样的特殊字符串。您还可以定义一个字符串,例如

val NoId = "?"

def div(id: String = NoId) = id match {
  case NoId => None
  case x    => Some(x)
}

另一种方法是使用另一种可以从字符串或缺席中隐式创建的类型。

sealed trait MaybeId
implicit class Id(val name: String) extends MaybeId
case object NoId extends MaybeId

def div(id: MaybeId = NoId) = id match { 
  case NoId  => None
  case x: Id => Some(x.name)
}

Here是一种通用类型,其行为类似Option[A],具有隐式转化A => Some[A]