特征和序列化/反序列化

Ant*_*nyF 6 serialization scala traits

假设我有两个特征,我想混合到一个班级.每个特征都实现了类所需的抽象方法.

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
}
Run Code Online (Sandbox Code Playgroud)

然后我可以用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
Run Code Online (Sandbox Code Playgroud)

一切都按预期工作.但是,如何在尊重混入M的特征类型的同时反序列化对象?我可以在序列化方法中输出特征的名称,然后在名称上使用M的反序列化方法调度,但如果我有除M以外的类,A和B可以混合到什么?然后,每个类都必须复制M的调度反序列化的行为.如果我有多个特征需要混合到一个对象中以使其具体化并且每个特征都有自己的自定义序列化/反序列化,那么问题会变得更糟.有人解决这样的问题吗?

Eug*_*ota 5

是的,人们有.要走的路是使用David MacIver的sbinary和Debasish Ghosh的sjson所倡导的类型模式.Debasish的三部曲

对所有中级Scala程序员特别有用.

今天,许多图书馆正在采用这种方法,包括我的scalaxb.看到

我从Scala Collections中借用了命名思想CanBuildFrom,并将我的类型命名如下:

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]
Run Code Online (Sandbox Code Playgroud)

编辑:

你能解释一下框架在"with A"或"with B"之间选择的方式吗?

使用类型类模式,库既不混合A也不混合B.以scalaxb为例,它提供了一个scalaxb.fromXML在包对象中调用的方法,定义如下:

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)
}
Run Code Online (Sandbox Code Playgroud)

鉴于您有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>)
Run Code Online (Sandbox Code Playgroud)

Address使用类型类模式,对象保持纯粹:

case class Address(name: String, street: String, city: String)
Run Code Online (Sandbox Code Playgroud)

编译器如何知道该怎么做?神奇的是被fromXML调用所需的隐式参数implicit format: XMLFormat[A].这要求您XMLFormat[Address]scalaxb.fromXML[ipo.Address](...)调用的作用域内作为隐式值可用.

这在scalaxb生成的代码中可用,因为它混入XMLProtocol了包的包对象中ipo.并ipo.XMLProtocol定义

implicit lazy val IpoAddressFormat: scalaxb.XMLFormat[ipo.Address] = new DefaultIpoAddressFormat {}
Run Code Online (Sandbox Code Playgroud)

编辑2:

我想我开始明白实际问题了.你有一个由特质mixin组成的对象,你想以某种方式"反序列化"其他过程中的特征组合.正如您所写,您可以为每个特征添加一些标记,并加载任何可能的内容.

由于我到目前为止已经写过类型模式,让我继续这个方法.在对象之外使用序列化代码的巧妙之处在于,您实际上可以描述该对象的mixin组合.假设有特征

trait Foo { def foo: Int }
trait Bar { def bar: Int }
Run Code Online (Sandbox Code Playgroud)

你想把mixin描述为<obj><foo>1</foo><bar>2</bar></obj>.这是我鞭打的要点.我定义的类型类的实例Foo,Bar以及Foo with Bar,并呼吁

Def.fromXML[Foo with Bar](<obj><foo>1</foo><bar>2</bar></obj>)
Run Code Online (Sandbox Code Playgroud)

哪个回来了

Right(FooWithBar(1, 2))
Run Code Online (Sandbox Code Playgroud)