抽象类中的不可变复制更新

时间:2013-12-21 17:20:38

标签: scala

我希望我的类既可以是不可变的又可以更新的。这是我的样板:

case class A(a: Int, b: Int) {
  def withB(newB: Int) = copy(b=newB)
}

它按预期工作。问题是,如果我希望客户端派生A但不直接实例化它会怎么样?这样:

abstract case class A(a: Int, b: Int) {
  def withB(newB: Int) = copy(b=newB)
}

不起作用。 (当然不是,如何在没有实例化的情况下复制自己?)

2 个答案:

答案 0 :(得分:1)

您需要开始查看其他模式以帮助您更新不可变对象。开始查看Lens类型。他们的工作方式如下:

trait Lens[A,B]{
  self =>

  def get(obj: A): B
  def set(obj: A, value: B): A

  def andThen(that: Lens[B, C]): Lens[A,C]{
    def get(obj: A) = that get (self get obj)
    def set(obj: A, value: C) = self set (obj, that set(self get obj, value))
  }
}

这样你就可以定义一个“get”和“set”,它既与纯粹(它们通过制作副本一起工作)和Scala中大多数事物的可组合性质保持一致。因此,在您的示例中,您不会在对象本身上定义withTwo方法,而是为其创建Lens

使用镜头:

case class Bar(c: Int)
case class Foo(a: Int, b: Bar)

object LensFooBar extends Lens[Foo, Bar]{
  def get(foo: Foo) = foo.b
  def set(foo: Foo, bar: Bar) = foo.copy(b = bar)
}
object LensBarC extends Lens[Bar, Int]{
  def get(bar: Bar) = bar.c
  def set(bar: Bar, value: Int) = bar.copy(c = value)
}

val myFirst = Foo(1, Bar(2))
val updated = LensFooBar.set(myFirst, Bar(3))

val lenser = LensFooBar andThen LensBarC
val updatedFurther = lenser set(myFirst, 3)

这两个镜头都在做同样的事情,但这不仅应该说明如何使用不可变的嵌套结构,还应该说明如何使用不可变对象,而不是为要在类中更改的每个字段放置一个方法。

您可以看到此Scala Days 2013 talk或了解Scalaz如何实施镜头here

答案 1 :(得分:0)

  

如果我希望客户端派生A但不直接实例化它呢?

您可以使用Builder模式执行此操作。您的构建器将是一个(case)类,其中包含创建A所需的所有内容,您可以将其传递到需要实例化A的位置。

例如,

case class ABuilder(one: Int = 1, two: String = "") {
  def withOne(v: Int) = copy ...
  def withTwo(v: String) = copy ...
  def build(): A = ...
}