将通用伴随对象传递给超级构造函数

Lev*_*Lev 1 generics json scala companion-object

我正在尝试通过消息(在 Akka 游戏环境中)构造 atraitabstract classto 子类型,以便我可以轻松地将它们转换为Json.

到目前为止做了什么:

    abstract class OutputMessage(val companion: OutputMessageCompanion[OutputMessage]) {
        def toJson: JsValue =  Json.toJson(this)(companion.fmt)
    }


    trait OutputMessageCompanion[OT] {
        implicit val fmt: OFormat[OT]
    }
Run Code Online (Sandbox Code Playgroud)

问题是,当我尝试按如下方式实现上述接口时:

    case class NotifyTableChange(tableStatus: BizTable) extends OutputMessage(NotifyTableChange)

    object NotifyTableChange extends OutputMessageCompanion[NotifyTableChange] {
        override implicit val fmt: OFormat[NotifyTableChange] = Json.format[NotifyTableChange]
    }

Run Code Online (Sandbox Code Playgroud)

我从 Intellij 收到此错误: Type mismatch, expected: OutputMessageCompanion[OutputMessage], actual: NotifyTableChange.type

我对 Scala 泛型有点陌生 - 所以对一些解释的帮助将不胜感激。

PS我愿意接受比提到的更通用的解决方案。目标是,在获取任何子类型OutputMessage- 时,轻松将其转换为Json.

Ser*_*gGr 5

编译器说您companion的定义是OutputMessage作为泛型参数而不是某些特定子类型。要解决此问题,您需要使用一种称为F-bound generic的技巧。此外,我不喜欢val在每条消息中将该伴随对象存储为 a 的想法(毕竟您不希望它被序列化,是吗?)。def恕我直言,将其定义为 a是更好的权衡。代码会像这样(同伴保持不变):

abstract class OutputMessage[M <: OutputMessage[M]]() {
    self: M => // required to match Json.toJson signature

    protected def companion: OutputMessageCompanion[M]

    def toJson: JsValue =  Json.toJson(this)(companion.fmt)
}

case class NotifyTableChange(tableStatus: BizTable) extends OutputMessage[NotifyTableChange] {

    override protected def companion: OutputMessageCompanion[NotifyTableChange] = NotifyTableChange
}
Run Code Online (Sandbox Code Playgroud)

您还可以查看标准 Scala 集合以实现相同方法。

但是如果你需要的companion只是用 JSON 格式编码,你可以像这样摆脱它:

  abstract class OutputMessage[M <: OutputMessage[M]]() {
    self: M => // required to match Json.toJson signature

    implicit protected def fmt: OFormat[M]

    def toJson: JsValue = Json.toJson(this)
  }

  case class NotifyTableChange(tableStatus: BizTable) extends OutputMessage[NotifyTableChange] {

    override implicit protected def fmt: OFormat[NotifyTableChange] = Json.format[NotifyTableChange]
  }
Run Code Online (Sandbox Code Playgroud)

显然,您还想从 JSON 解码,您仍然需要一个伴随对象。


对评论的回答

  1. 通过 def - 引用同伴意味着这是一个“方法”,因此为子类型的所有实例定义一次(并且不会被序列化)?

您声明的所有内容val都会在对象(类的实例)中存储一个字段。默认情况下,序列化程序尝试序列化所有字段。通常有一些方法可以说某些字段应该被忽略(比如 some @IgnoreAnnotation)。这也意味着您将在每个对象中多一个指针/引用,这些对象无缘无故地使用内存,这对您来说可能是也可能不是问题。将其声明为def获取方法,这样您就可以将一个对象存储在一些“静态”位置,例如伴随对象,或者每次都按需构建它。

  1. 我对 Scala 有点陌生,而且我养成了将格式放在伴生对象中的习惯,你会推荐/参考一些来源,关于如何决定最好把你的方法放在哪里吗?

Scala 是一种不寻常的语言,并且没有直接映射涵盖object其他语言中该概念的所有用例。作为第一条经验法则, 有两种主要用法object

  1. 您会static在其他语言中使用的东西,即静态方法、常量和静态变量的容器(尽管不鼓励使用变量,尤其是 Scala 中的静态变量)

  2. 单例模式的实现。

  1. 通过 f-bound generic - 你的意思是 M 的下限是 OutputMessage[M] (顺便说一句,为什么可以在同一个 expr 中使用 M 两次?)

不幸的是 wiki 只提供了一个基本的描述。F 有界多态的整个思想是能够以某种通用方式访问基类类型中的子类类型。通常A <: B约束意味着A应该是 的子类型B。这里 with M <: OutputMessage[M],它意味着M应该是 的子类型,OutputMessage[M]它可以很容易地通过声明子类来满足(还有其他不简单的方法来满足它)为:

class Child extends OutputMessage[Child}
Run Code Online (Sandbox Code Playgroud)

这种技巧允许您将M用作方法中的参数或结果类型。

  1. 我对 self 位有点困惑......

最后,这个self位是另一个必要的技巧,因为在这种特殊情况下 F 有界多态性是不够的。通常trait在将 trait 用作mix-in时使用它。在这种情况下,您可能希望限制 trait 可以混合在哪些类中。并且在相同类型下,它允许您在 mixin 中使用来自该基类型的方法trait

我会说我的答案中的特定用法有点不合常规,但它具有相同的双重效果:

  1. 编译时OutputMessage,编译器可以假设该类型也将以某种方式属于M(无论M是什么)

  2. 在编译任何子类型编译器时,确保满足约束 #1。例如它不会让你做

abstract class OutputMessage[M <: OutputMessage[M]]() {
    self: M => // required to match Json.toJson signature

    protected def companion: OutputMessageCompanion[M]

    def toJson: JsValue =  Json.toJson(this)(companion.fmt)
}

case class NotifyTableChange(tableStatus: BizTable) extends OutputMessage[NotifyTableChange] {

    override protected def companion: OutputMessageCompanion[NotifyTableChange] = NotifyTableChange
}
Run Code Online (Sandbox Code Playgroud)

其实self:M反正我也得用,你大概可以把这里的F-bounded部分去掉,就这样吧

abstract class OutputMessage[M]() {
    self: M =>
     ...
}
Run Code Online (Sandbox Code Playgroud)

但我会坚持下去以更好地传达含义。