简体   繁体   English

特征和序列化/反序列化

[英]Traits and serialization/deserialization

Say I have two traits that I would like to mixin to a class. 假设我有两个特征,我想混合到一个班级。 The traits each implement an abstract method that the class needs. 每个特征都实现了类所需的抽象方法。

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
}

I can then construct a concrete M with either A or B and serialize: 然后我可以用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

Everything works as expected. 一切都按预期工作。 But how do I deserialize the objects while respecting the type of trait that is mixed into M? 但是,如何在尊重混入M的特征类型的同时反序列化对象? I could output the trait's name in the serialize method and then have M's deserialize method dispatch on the name but what if I have classes other than M that A and B can be mixed into? 我可以在序列化方法中输出特征的名称,然后在名称上使用M的反序列化方法调度,但如果我有除M以外的类,A和B可以混合到什么? Then, each class would have to duplicate the behavior of M's dispatching deserialize. 然后,每个类都必须复制M的调度反序列化的行为。 The problem gets even worse if I have multiple traits that need to get mixed into an object to make it concrete and each has it's own custom serialization/deserialization to do. 如果我有多个特征需要混合到一个对象中以使其具体化并且每个特征都有自己的自定义序列化/反序列化,那么问题会变得更糟。 Anyone tackle an issue like this? 有人解决这样的问题吗?

Yes, people have. 是的,人们有。 The way to go is to use typeclass pattern championed by David MacIver's sbinary and Debasish Ghosh's sjson . 要走的路是使用David MacIver的sbinary和Debasish Ghosh的sjson所倡导的类型模式。 Debasish's trilogy Debasish的三部曲

are especially useful to all intermediate Scala programmers. 对所有中级Scala程序员特别有用。

Today, many of the libraries are adopting this methodology including mine scalaxb . 今天,许多图书馆正在采用这种方法,包括我的scalaxb See 看到

I've borrowed the naming idea from Scala Collections' CanBuildFrom and named my typeclasses as follows: 我从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]

Edit : 编辑

Could you explain to me how the framework chooses between "with A" or "with B"? 你能解释一下框架在“with A”或“with B”之间选择的方式吗?

Using the typeclass pattern, the libraries mixes in neither A nor B . 使用类型类模式,库既不是A也不是B混合。 To take scalaxb for example, it provides a method called scalaxb.fromXML in the package object defined as follows: 以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)
}

Given that you have XML document, and you want to unmarshal (deserialize) it to ipo.Address object, you would call 鉴于您有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>)

The Address object stays pure using the typeclass pattern: Address对象使用类型类模式保持纯粹:

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

How does the compiler know what to do? 编译器如何知道该怎么做? The magic is the implicit parameter required by fromXML called implicit format: XMLFormat[A] . 魔术是fromXML所需的隐式参数,称为implicit format: XMLFormat[A] This requires that you have XMLFormat[Address] available as an implicit value within the scope where scalaxb.fromXML[ipo.Address](...) is called. 这要求您在scalaxb.fromXML[ipo.Address](...)的范围内将XMLFormat[Address]作为隐式值提供。

This made available in the code generated by scalaxb because it mixes in XMLProtocol into the package object of ipo package. 这在scalaxb生成的代码中可用,因为它将XMLProtocol混合到ipo包的包对象中。 And ipo.XMLProtocol defines ipo.XMLProtocol定义

implicit lazy val IpoAddressFormat: scalaxb.XMLFormat[ipo.Address] = new DefaultIpoAddressFormat {}

Edit2 : 编辑2

I think I am starting to understand the actual question. 我想我开始明白实际问题了。 You have an object consisting of trait mixins, and you want to somehow "deserialize" the trait composition on other process. 你有一个由特质mixin组成的对象,你想以某种方式“反序列化”其他过程中的特征组合。 As you wrote, you could include some tag for each traits, and load whatever you can. 正如您所写,您可以为每个特征添加一些标记,并加载任何可能的内容。

Since I've written so far on typeclass pattern, let me continue with the approach. 由于我到目前为止已经写过类型模式,让我继续这个方法。 The neat thing about having serialization code outside of the object is that you could actually describe mixin combination of the object. 在对象之外使用序列化代码的巧妙之处在于,您实际上可以描述该对象的mixin组合。 Suppose there are traits 假设有特征

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

and you want to describe the mixin as <obj><foo>1</foo><bar>2</bar></obj> . 并且您想将mixin描述为<obj><foo>1</foo><bar>2</bar></obj> Here's a gist I whipped up. 这是我鞭打的要点 I defined typeclass instance for Foo , Bar , and Foo with Bar , and called Foo with BarFooBarFoo with Bar定义了类型类实例,并进行了调用

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

which returned 哪个回来了

Right(FooWithBar(1, 2))

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM