如何提供辅助方法来构建地图 [英] How to provide helper methods to build a Map
问题描述
我有很多使用相同的键(查询MongoDB)构建 Map
的客户端代码。
I have a lot of client code that build Map
using the same keys (to query MongoDB).
我的想法是提供隐藏键的辅助方法。
My idea is to provide helper methods that hide keys.
首先尝试使用默认参数(参见 object Builder
以下),但客户端需要处理 Option
First try, I have used default parameters (cf object Builder
below) but the client hava to deal with Option
我现在使用了构建器模式(cf class Builder
下面)
I now use a builder pattern (cf class Builder
below)
还有更好的方法吗?
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))
}
}
推荐答案
一种简单的解决方案,避免在调用 Builder.build1
定义一个隐式转换,以自动将任何值包装为 Some
:
An easy solution to avoid having to deal with options when calling Builder.build1
is to define an implicit conversion to automatically wrap any value into an Some
:
implicit def wrap[T]( x: T ) = Some( x )
繁荣,您可以省略包装并直接这样做:
And boom, you can omit the wrapping and directly do:
scala> Builder.build1( a = 123, c = 456 )
res1: Map[String,Int] = Map(a -> 123, c -> 456)
但是,鉴于期权无处不在,并且您不希望将这种一般的转换纳入范围,这非常危险。
要解决此问题,您可以定义自己的 option类,仅用于定义这些可选参数:
However, this is pretty dangerous given that options are pervasive and you don't want to pull such a general converion into scope. To fix this you can define your own "option" class that you'll use just for the purpose of defining those optional parameters:
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
而不显式地包装参数w ith Some
:
And then once again, you can directly call Build.build1
without explicitely wrapping the argument with Some
:
scala> Builder.build1( a = 123, c = 456 )
res1: Map[String,Int] = Map(a -> 123, c -> 456)
与现在的显着区别是,我们不再将危险的广泛转换为应付。
With the notable difference that now we are not pulling anymore a dangerously broad conversion into cope.
更新:为响应下的注释,为了进一步满足我的需要,arg可以是单个值或列表,而我今天我的客户代码中有可怕的Some(List(sth))。
您可以添加另一种转换,以将单个参数包装到一个元素列表中:
You can add another conversion to wrap individual parameters into one element list:
implicit def autoWrapAsList[T]( value: T ): OptionalArg[List[T]] = SomeArg(List(value))
然后说您的方法需要这样的可选列表:
Then say that your method expects an optional list like this:
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: _*)
}
您现在可以传递单个元素或列表(或像以前一样,根本没有参数):
You can now either pass an individual element or a list (or just like before, no argument at all):
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)
最后一个警告:即使我们定义了自己的 option类,您还是应该谨慎使用隐式转换,这确实是正确的,
还是要花一些时间来平衡在您的用例中,便利性是否值得冒险。
One last warning: even though we have defined our very own "option" class, it is still true that you should always use implicit conversions with some care, so take some time to balance whether the convenience is worth the risk in your use case.
这篇关于如何提供辅助方法来构建地图的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!