Scala案例类的setter

时间:2016-10-17 21:09:23

标签: scala

我希望为案例类提供灵活的生成器。

case class ABC(a: String, b: String, c: String)

我想以任何顺序写作:

val genAbc = (new Gen).withB("b").withA("a").withC("c")

如果设置了所有参数,则能够将其转换为case类:

genAbc.toAbc

如果没有设置所有参数,我想得到编译错误:

(new Gen).withA("a").toAbc //<- error

有可能吗?

3 个答案:

答案 0 :(得分:4)

Scala中有一个类型安全版本的构建器模式,请参阅http://blog.rafaelferreira.net/2008/07/type-safe-builder-pattern-in-scala.html

但是,如果你愿意放弃能够以任何顺序设置参数的要求,那么有一种更简单的方法:

scala> val abcBuilder = (Abc.apply _).curried
abcBuilder: String => (String => (String => Abc)) = <function1>

使用Abc类的智能构造函数(Abc.apply)创建 curried函数。 curried函数允许您分别传入每个参数。在此期间,您不断获得更多函数,这些函数采用一个参数并使您更接近Abc实例。所以:

scala> abcBuilder("a")
res11: String => (String => Abc) = <function1>

scala> res11("b")
res12: String => Abc = <function1>

scala> res12("c")
res13: Abc = Abc(a,b,c)

这也是类型安全的(在某种程度上你使用不同的参数类型);所以例如abcBuilder(1)会给你一个编译错误。

答案 1 :(得分:1)

可以做这样的事情:

class Gen {
        def withA(a: String) = GenWithA(a)
        def withB(b: String) = GenWithB(b)
        def withC(c: String) = GenWithC(c)
}

case class GenWithA(a: String) {
        def withB(b: String) = GenWithAB(a, b)
        def withC(c: String) = GenWithAC(a, c)
}

case class GenWithB(b: String) {
        def withA(a: String) = GenWithAB(a, b)
        def withC(c: String) = GenWithBC(b, c)
}

case class GenWithC(c: String) {
        def withA(a: String) = GenWithAC(a, c)
        def withB(b: String) = GenWithBC(b, c)
}

case class GenWithAB(a: String, b: String) {
        def withC(c: String) = GenWithABC(a,b,c)
}

case class GenWithAC(a: String, c: String) {
        def withB(b: String) = GenWithABC(a,b,c)
}

case class GenWithBC(b: String, c: String) {
        def withA(a: String) = GenWithABC(a,b,c)
}

case class GenWithABC(a: String, b: String, c: String) {
        def toABC = ABC(a,b,c)
}

这里有两个问题:

首先,当你开始超过3个参数时,你会得到组合爆炸。您可以在SBT中生成它,也可以使用宏或无形魔法生成它 可能但都是很多工作。

其次,这种代码无法按照您希望的方式进行类型检查:

val g0 = new Gen

val g1 = if (condition) g0.withA("a") else g0.withB("b")

val g2 = if (condition) g1.withB("b") else g1.withA("a")

val g3 = g2.withC("c")

从理论上讲,这总是产生一些有效的东西,但scala编译器 是不够聪明,弄清楚,所以你不能使用的不同部分 不同条件分支的建设者。

这并不会让你失望。如果您的目标是简单地以任何顺序编写参数,那么更简单的方法是将它们指定为命名参数:

ABC(
  c = "C First",
  b = "Then B",
  a = "Finally A"
)

对于每个阶段都有类似的构建器,例如,在构建查询DSL时,但是对于构建数据案例类,它几乎总是笨重的过度杀伤

答案 2 :(得分:1)

也许你正在寻找像case class copy方法那样简单的东西(你确实不能错过其中一个参数,你可以按随机顺序指定它们):

case class ABC(a: String, b: String, c: String)
val abc = ABC("a", "b", "c")
println(abc)
// ABC(a,b,c)
val acc = abc.copy(b = "c")
println(acc)
// ABC(a,c,c)