如何提供帮助方法来构建Map

时间:2014-02-06 13:00:50

标签: scala builder scala-collections

我有很多使用相同密钥构建Map的客户端代码(用于查询MongoDB)。

我的想法是提供隐藏密钥的辅助方法。

首先尝试,我使用了默认参数(参见下面的object Builder),但客户hava要处理Option

我现在使用构建器模式(参见下面的class Builder

有更好的方法吗?

class Builder {
  val m = collection.mutable.Map[String, Int]()
  def withA(a: Int) = {m += (("a", a))}
  def withB(b: Int) = {m += (("b", b))}
  def withC(c: Int) = {m += (("c", c))}
  def build = m.toMap
}

object Builder {
  def build1(a: Option[Int] = None, b: Option[Int] = None, c: Option[Int] = None): Map[String, Int] = {
    val optPairs = List(a.map("a" -> _),
      b.map("b" -> _),
      c.map("c" -> _))
    val pairs = optPairs.flatten
    Map(pairs: _*)
  }
}

object Client {
  def main(args: Array[String]) {
    println(Builder.build1(b = Some(2)))

    println(new Builder().withB(2))
  }
}

1 个答案:

答案 0 :(得分:2)

在调用Builder.build1时避免必须处理选项的简单解决方案是定义隐式转换以自动将任何值包装到Some中:

implicit def wrap[T]( x: T ) = Some( x )

繁荣,你可以省略包装并直接做:

scala> Builder.build1( a = 123, c = 456 )
res1: Map[String,Int] = Map(a -> 123, c -> 456)

然而,这是非常危险的,因为选项是普遍存在的,并且您不希望将这种一般的转换拉入范围。 要解决此问题,您可以定义自己的“选项”类,仅用于定义这些可选参数:

abstract sealed class OptionalArg[+T] {
  def toOption: Option[T]
}
object OptionalArg{
  implicit def autoWrap[T]( value: T  ): OptionalArg[T] = SomeArg(value)
  implicit def toOption[T]( arg: OptionalArg[T] ): Option[T] = arg.toOption
}
case class SomeArg[+T]( value: T ) extends OptionalArg[T] {
  def toOption = Some( value )
}
case object NoArg extends OptionalArg[Nothing] {
  val toOption = None
}

然后,您可以将Build.build1重新定义为:

def build1(a: OptionalArg[Int] = NoArg, b: OptionalArg[Int] = NoArg, c: OptionalArg[Int] = NoArg): Map[String, Int]

然后再一次,你可以直接调用Build.build1而不用Some明确地包装参数:

scala> Builder.build1( a = 123, c = 456 )
res1: Map[String,Int] = Map(a -> 123, c -> 456)

由于显着的差异,现在我们不再采取危险的广泛转变为应对措施。


更新:为了回应下面的评论“,我需要更多的东西,arg可以是单个值或列表,而且我有一些(List(某事))在我的客户端代码中“

您可以添加另一个转换以将单个参数包装到一个元素列表中:

implicit def autoWrapAsList[T]( value: T  ): OptionalArg[List[T]] = SomeArg(List(value))

然后说你的方法需要一个像这样的可选列表:

def build1(a: OptionalArg[List[Int]] = NoArg, b: OptionalArg[Int] = NoArg, c: OptionalArg[Int] = NoArg): Map[String, Int] = {
  val optPairs = List(a.map("a" -> _.sum),
    b.map("b" -> _),
    c.map("c" -> _))
  val pairs = optPairs.flatten
  Map(pairs: _*)
}

您现在可以传递单个元素或列表(或者像之前一样,根本不传递任何参数):

scala> Builder.build1( a = 123, c = 456 )
res6: Map[String,Int] = Map(a -> 123, c -> 456)

scala> Builder.build1( a = List(1,2,3), c = 456 )
res7: Map[String,Int] = Map(a -> 6, c -> 456)

scala> Builder.build1( c = 456 )
res8: Map[String,Int] = Map(c -> 456)

最后一个警告:即使我们已经定义了我们自己的“选项”类,但仍然应该始终谨慎使用隐式转换, 因此,请花一些时间来平衡您的用例中的便利是否值得冒险。