如何从scala案例类中装饰不可变对象图

时间:2015-02-24 13:41:05

标签: json scala playframework immutability case-class

我正在阅读结构化JSON,使用Play Frameworks' JSON读取以构建具有案例类的对象图。

一个例子:

case class Foo (
                       id: Int,
                       bar_id: Int,
                       baz_id: Int,
                       x: Int,
                       y: String
                       )
{
  var bar: Bar = null
  var baz: Baz = null
}

在建造Foo之后,我必须稍后回来并通过设置bar和baz来装饰它。这些在其他JSON文件中定义,仅在所有解析完成时才知道。但这意味着Foo不可变。

什么是"权利"在Scala中创建一个不可变对象,然后是它的装饰版本,而不是一遍又一遍地重复Foo的每个字段?

我知道有几种感觉不对的方法:

  • make" bar:选项[Bar]"和" baz:选项[Baz]"案例类参数,然后我可以使用" copy"将Foo类的新版本设置为某些东西;但是每次他们被访问时我都必须检查它们 - 效率低,不安全,无法制作一个只保证具有正确结构的DecorativeFoo
  • 制作第二个案例类,它是第一个案例中所有结构的复制粘贴,但添加了两个额外的修饰参数 - 但这意味着在定义中回显整个参数列表,并在创建它的实例时再次< / LI>
  • 案例类继承显然存在争议,无论如何似乎还要求我在子类构造函数中重复每一个参数?
  • 创建一个列出常见案例类参数的非案例超类。然后在case类中扩展它。但这似乎仍然需要重复子类构造函数中的每个参数。
  • 我看到人们谈论这个问题的博客,并在运行时使用反射来填充他们的装饰副本的基本属性 - 这避免了回声,但现在你没有类型安全,指定属性名称为字符串,开销等...

当然,Scala必须有办法让人们用更简单的对象组成更复杂的不可变对象,而不必手工复制它们的每一部分?

3 个答案:

答案 0 :(得分:1)

另一种策略可能是创建另一个案例类:

case class Foo(
  id: Int,
  bar_id: Int,
  baz_id: Int,
  x: Int,
  y: String
)

case class ProcessedFoo(
  foo: Foo,
  bar: Bar,
  baz: Baz
)

答案 1 :(得分:1)

您可以为已处理的类型引入新特征,扩展该特征的类以及隐式转换:

case class Foo(bar: Int)

trait HasBaz {
    val baz: Int
}

class FooWithBaz(val foo: Foo, val baz: Int) extends HasBaz

object FooWithBaz {
    implicit def innerFoo(fwb: FooWithBaz): Foo = fwb.foo

    implicit class RichFoo(val foo: Foo) extends AnyVal {
        def withBaz(baz: Int) = new FooWithBaz(foo, baz)
    }
}

那么你可以这样做:

import FooWithBaz._
Foo(1).withBaz(5)

而且,尽管withBaz返回FooWithBaz,但由于隐式转换,我们可以在必要时将返回值视为Foo

答案 2 :(得分:1)

结合Option和类型参数,您可以标记您的案例类,并静态地跟踪已处理的字段是否为空:

import scala.language.higherKinds

object Acme {
  case class Foo[T[X] <: Option[X] forSome { type X }](a: Int,
                                                       b: String,
                                                       c: T[Boolean],
                                                       d: T[Double])

  // Necessary, Foo[None] won't compile
  type Unprocessed[_] = None.type
  // Just an alias
  type Processed[X] = Some[X]
}

示例用例:

import Acme._

val raw: Foo[Unprocessed] = Foo[Unprocessed](42, "b", None, None)

def process(unprocessed: Foo[Unprocessed]): Foo[Processed] =
  unprocessed.copy[Processed](c = Some(true), d = Some(42d))

val processed: Foo[Processed] = process(raw)

// No need to pattern match, use directly the x from the Some case class
println(processed.c.x)
println(processed.d.x)

我在当前项目中使用过一次。我遇到的主要问题是我希望Foo是协变的。


或者,如果您不关心T上的界限:

case class Foo[+T[_]](a: Int, b: String, c: T[Boolean], d: T[Double])

然后,当您需要Foo[Unprocessed]时,可以使用Foo[Processed]Foo[Option]

scala> val foo: Foo[Option] = processed
foo: Acme.Foo[Option] = Foo(42,b,Some(true),Some(42.0))