特征和序列化/反序列化

6

假设我有两个特性,我想将它们混入到一个类中。这些特性都实现了该类需要的抽象方法。

trait Writable {
    def serialize(out: java.io.DataOutput)
}

trait T1 extends Writable

trait A extends T1 {
   val aNum: Int
   abstract override def serialize(out: java.io.DataOutput) = {
       super.serialize(out)
       println("A serialize")
       out.writeInt(aNum)
   }

   def action = println("A action")
}

trait B extends T1 {
   val bNum: Int
   abstract override def serialize(out: java.io.DataOutput) = {
       super.serialize(out)
       println("B serialize")
       out.writeInt(bNum)
   }

   def action = println("B action")
}

abstract class M[CT1 <: T1](val mNum: Int) extends Writable {
   this: M[CT1] with T1 =>
   def serialize(out: java.io.DataOutput) = {
       println("M serialize")
       out.writeInt(mNum)
   }

   def action
}

我可以使用 A 或 B 构建一个具体的 M 并将其序列化:

scala> val m1 = new M[A](10) with A { val aNum = 20 }
m1: M[A] with A = $anon$1@67c1e630

scala> val m2 = new M[B](20) with B { val bNum = 30 }
m2: M[B] with B = $anon$1@c36f58e

scala> val out = new java.io.DataOutputStream(new java.io.ByteArrayOutputStream())
out: java.io.DataOutputStream = java.io.DataOutputStream@87afebf

scala> m1.serialize(out)
M serialize
A serialize

scala> m2.serialize(out)
M serialize
B serialize

一切都按预期工作。但是,如何在尊重混入M的特质类型的同时反序列化对象?我可以在serialize方法中输出特质的名称,然后让M的deserialize方法根据名称进行调度,但如果除了M之外还有其他类可以将A和B混入其中怎么办?那么,每个类都必须复制M的调度反序列化行为。如果我有多个需要混入对象以使其具体化并且每个对象都有自己的自定义序列化/反序列化的特质,则问题会变得更加严重。有人解决过这样的问题吗?
1个回答

5
是的,有人用过。选择使用David MacIver的sbinary和Debasish Ghosh的sjson所倡导的typeclass模式。Debasish的三部曲 对所有中级Scala程序员特别有用。
今天,许多库都采用了这种方法,包括我的scalaxb。看看: 我从Scala Collections的CanBuildFrom借鉴了命名思路,并将我的typeclass命名如下:
trait CanReadXML[A] {
  def reads(seq: scala.xml.NodeSeq): Either[String, A]
}

trait CanWriteXML[A] {
  def writes(obj: A, namespace: Option[String], elementLabel: Option[String],
      scope: NamespaceBinding, typeAttribute: Boolean): NodeSeq
}

trait XMLFormat[A] extends CanWriteXML[A] with CanReadXML[A]

编辑:

你能解释一下框架是如何在“with A”和“with B”之间进行选择的吗?

使用类型类模式,库既不混入A也不混入B。 以scalaxb为例,它提供了一个名为scalaxb.fromXML的方法,定义在package object中,如下所示:

def fromXML[A](seq: NodeSeq, stack: List[ElemName] = Nil)
              (implicit format: XMLFormat[A]): A = format.reads(seq, stack) match {
  case Right(a) => a
  case Left(a) => throw new ParserFailure(a)
}

假设您拥有XML文档,想要将其反序列化为ipo.Address对象,您需要调用以下代码:

scalaxb.fromXML[ipo.Address](<shipTo xmlns="http://www.example.com/IPO">
  <name>Foo</name>
  <street>1537 Paper Street</street>
  <city>Wilmington</city>
</shipTo>)
< p >使用类型类模式,Address对象保持纯净:

case class Address(name: String, street: String, city: String)

编译器如何知道该做什么?神奇的是,fromXML 隐含参数 implicit format: XMLFormat[A]。这需要您在调用 scalaxb.fromXML[ipo.Address](...) 的作用域内有 XMLFormat[Address] 作为隐式值可用。
这是由 scalaxb 生成的代码中提供的,因为它将 XMLProtocol 混合到 ipo 包的 package object 中。而 ipo.XMLProtocol 定义了。
implicit lazy val IpoAddressFormat: scalaxb.XMLFormat[ipo.Address] = new DefaultIpoAddressFormat {}

编辑2:

我认为我开始理解实际问题了。您有一个由特质混合组成的对象,并且您希望以某种方式在其他进程上“反序列化”特质组合。正如您所写的,您可以为每个特质包含一些标记,并加载您可以的任何内容。

由于我迄今为止都是按照类型类模式编写的,因此让我继续使用这种方法。将序列化代码放在对象之外的好处在于,您实际上可以描述对象的混合组合。假设存在特质

trait Foo { def foo: Int }
trait Bar { def bar: Int }

您想把混合对象描述为<obj><foo>1</foo><bar>2</bar></obj>。这是我编写的一个代码片段。我为FooBarFoo with Bar定义了类型类实例,并进行了调用。

Def.fromXML[Foo with Bar](<obj><foo>1</foo><bar>2</bar></obj>)

返回的是

Right(FooWithBar(1, 2))

我不清楚这些库在反序列化时如何选择要混入到类中的特征。你能解释一下框架是如何在“with A”和“with B”之间进行选择的吗? - AnthonyF
因此,Trait A 和 Trait B 在 M 类中包含不同的抽象方法实现。我已编辑问题以反映这个事实。我希望用 traits 定义行为来构建一个对象,并允许该对象进行序列化和反序列化。换句话说,具体的 M 实例不仅仅是一个数据类,它具有特定的行为。 - AnthonyF
使用反序列化/取消编组时,您需要将实现放在类外部,因为对象尚不存在。我已经演示了一种使用类型类实现此操作的方法,但如果您喜欢,也可以使用伴生对象来完成此操作。 - Eugene Yokota
我认为更好的描述是声明式依赖注入而不是序列化。 我试图序列化一个统计模型,以便可以将其传输到远程节点进行并行执行。 因此,我需要将其行为指定为序列化协议的一部分。 - AnthonyF

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接