【问题标题】:Create a companion object that mixes in a trait that defines a method which returns an object of the object's companion class创建一个伴生对象,该对象混合一个特征,该特征定义了一个返回对象伴生类对象的方法
【发布时间】:2016-07-02 12:57:57
【问题描述】:

抽象问题:创建一个可以混合到类的伴随对象中的特征,为该对象提供一个返回该类对象的方法。

具体问题:我正在尝试创建一组用于 RESTful 服务调用的类,它们知道如何对自身进行序列化和反序列化,如下所示:

case class Foo
(
    var bar : String,
    var blip : String
)
extends SerializeToJson
object Foo extends DeserializeFromJson

预期的用法是这样的:

var f = Foo( "abc","123" )
var json = f.json
var newF = Foo.fromJson( json )

我正在使用Genson 进行序列化/反序列化,我通过全局对象访问:

object JSON {
  val parser = new ScalaGenson( new GensonBuilder() <...> )
}

然后我像这样定义特征:

trait SerializeToJson {
  def json : String = JSON.parser.toJson(this)
}
trait DeserializeFromJson[T <: DeserializeFromJson[T]] {
  def fromJson( json : String ) : T = JSON.parser.fromJson( json )
}

这编译。但这不是:

object Foo extends DeserializeFromJson[Foo]

我收到以下错误消息:

type arguments [Foo] do not conform to trait DeserializeFromJson's 
type parameter bounds [T <: DeserializeFromJson[T]] 

我尝试过创建单一特征,如下所示:

trait JsonSerialization[T <: JsonSerialization[T]] {

  def json(implicit m: Manifest[JsonSerialization[T]]) : String = 
    JSON.parser.toJson(this)(m)

  def fromJson( json : String ) : T = 
    JSON.parser.fromJson(json)

}

现在,如果我只声明case class Foo (...) extends JsonSerialization[Foo],那么我不能调用Foo.fromJson,因为只有Foo 类的实例具有该方法,而不是伴随对象。

如果我声明object Foo extend JsonSerialization[Foo],那么我可以编译并且Foo 有一个.fromJson 方法。但是在运行时,对fromJson 的调用认为TJsonSerialization,而不是Foo,或者以下运行时错误表明:

java.lang.ClassCastException: scala.collection.immutable.HashMap$HashTrieMap cannot be cast to ...JsonSerialization
at ...JsonSerialization$class.fromJson(DataModel.scala:14)
at ...Foo.fromJson(Foo.scala:6)

我不能声明object Foo extends Foo,因为我得到了

module extending its companion class cannot use default constructor arguments

所以我可以尝试添加构造函数参数,然后编译并运行,但再次尝试反序列化时的运行时类型是错误的,出现上述错误。

我唯一能做的就是在每个伴随对象中定义fromJson。但是必须有一种方法可以在特征中定义它,并且只需混合该特征即可。对吧?

【问题讨论】:

    标签: scala generics serialization traits companion-object


    【解决方案1】:

    解决方案是简化 trait 的类型参数。

    trait DeserializeFromJson[T] { 
      def fromJson( json : String )(implicit m : Manifest[T]) : T = 
        JSON.parser.fromJson[T](json)(m)
    }
    

    现在,伴生对象可以扩展 DeserializeFromJson[Foo],当我调用 Foo.fromJson( json ) 时,它能够告诉 Genson 正确的类型信息,以便创建适当类型的对象。

    【讨论】:

      【解决方案2】:

      问题与隐式的工作方式有关。 Genson 期望有一个 Manifest 用于了解它必须反序列化的类型。此清单在 Genson 中被定义为隐式,这意味着它将尝试从“调用者代码”中隐式可用的清单中获取它。但是在您的原始版本中,DeserializeFromJson 中没有 Manifest[T]。

      另一种方法是像这样定义 DeserializeFromJson(它只会生成一个带有隐式 Manifest[T] 参数的构造函数):

      abstract class DeserializeFromJson[T: Manifest] {
        def fromJson( json : String ) : T = JSON.parser.fromJson[T](json)
      }
      
      object Foo extends DeserializeFromJson[Foo]
      

      更一般地说,如果您不通过封装库(在本例中为 Genson)带来更多价值,我认为您不应该这样做。因为您基本上减少了 Genson 的功能(现在人们只能使用字符串)并引入您遇到的问题。

      【讨论】:

      • 虽然我同意最后一条评论,但我不确定是否有更好的方法。目的是能够定义一个类,其唯一目的是用于在应用程序(在本例中为集成测试)和 REST 接口之间来回传递。所有类需要做的就是以可访问的方式(字段)存储数据,并知道如何序列化和反序列化自身。实现它的标准方法是什么?
      【解决方案3】:

      我认为您的类型参数约束最初是错误的; 你有

      trait DeserializeFromJson[T <: DeserializeFromJson[T]]
      

      有了自己的答案,你就彻底放松了;你需要

      trait DeserializeFromJson[T <: SerializeToJson]
      

      ...错误试图告诉你的。

      对隐式 Manifest(我相信现在是 ClassTag)或上下文边界的需求很重要。

      Scala 允许基于类/特征和伴生对象关系来规范继承和类型参数约束会很好,因为它在某种程度上已经意识到访问修饰符和隐式作用域。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2019-07-24
        • 2012-06-08
        • 2021-02-12
        • 2018-02-06
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多