Case class 禁止继承,但是如何从库中建模依赖?
Case class inheritance is prohibited, but how to model dependency from library?
我有三个不同的 Scala 项目,其中一个仅包含在其他两个 Scala 项目中使用的模型(以下称为库)。这三个项目之一是一个 Play 网络应用程序,我想将库中的模型保存在 MongoDB 中(代码驻留在 Play 应用程序中)。
所以在我的库中我有以下模型:
case class MyUser(id: UUID = UUID.randomUUID(), timestamp: Instant = Instant.now, name: String)
要在我的 Play 应用程序中保留 MyUser
的实例,我通常会使用此模型:
case class MyUser(var _id: Option[BSONObjectID] = None,
id: UUID,
timestamp: Instant,
name: String,
var created: Option[DateTime] = None,
var updated: Option[DateTime] = None
) extends Temporal
现在我正在寻找一种方法将我的库 MyUser
变形为我的 Play 应用程序中相应的 class,所以我想我会做这样的事情:
case class MyUser(var _id: Option[BSONObjectID] = None,
var created: Option[DateTime] = None,
var updated: Option[DateTime] = None
) extends lib.MyUser with Temporal
但是,现在我得到以下错误:
case-to-case inheritance is prohibited. To overcome this limitation, use extractors to pattern match on non-leaf nodes.
我想过这样做:
case class MyUser(var _id: Option[BSONObjectID] = None,
user: lib.MyUser,
var created: Option[DateTime] = None,
var updated: Option[DateTime] = None
) extends Temporal
我怎样才能以正确的方式设计它?
首先,错误告诉你的是你不应该为扩展的 class 使用 case class
。它所说的关于提取器的事情基本上是,如果需要在父 class 上进行模式匹配(这是 case class
的 case
的来源,简单的模式匹配),你总是可以为它编写自定义提取器。
但是,这不适用于您的情况,因为您无法更改您的库公开 case class
es 的事实。
另一种解决方案是考虑到您的两个 classes 之间没有继承关系。实际上,一个是用户的表示,而另一个是包含用户的存储单元的表示。所以看起来你提出的解决方案毕竟有一些意义。
不过,无论是在序列化对象(在 Mongo 中)还是在 JVM class 中,都必须添加一个字段似乎很麻烦,您需要始终在其中执行 mongoUser.user
从 MyUser
...
得到 lib.MyUser
一个优雅的(恕我直言)解决方案是保留这个字段 user: lib.MyUser
,但是让你的序列化把这个字段压平到 BSON 对象的顶层,这样一个序列化的 MyUser
看起来喜欢
{
_id: ObjectId(_),
id: 123456789abc-1234-1234-1234,
...
}
就好像你继承了lib.MyUser
的领域一样。
反序列化
现在,只要您想从 Mongo 中获取 lib.MyUser
,仅供阅读,您可以直接以这种格式反序列化它,忽略添加的字段。但是,如果您需要对其进行一些更新,则必须将其反序列化为 MyUser
,以便能够更新此特定的 BSON 文档。
scala 用法
当您将对象反序列化为 MyUser
(例如,为了更新)时,您可能仍然希望轻松访问 lib.MyUser
中公开的所有字段,而无需访问该字段user
每次。这可以通过隐式转换来完成。
泛化
顺便说一下,您可以对任何您想要序列化的对象以通用方式执行此操作...
总结一下
case class MongoRepr[T](_id: Option[BSONObjectId]
value: T,
created: Option[DateTime],
updated: Option[DateTime])
object MongoRepr {
//implement flattened (de)serialization, this depends on your
// mongo driver and what you're using for serialization (JSON/BSON)
def handler[T](implicit handlerT: BSONHandler[T]): BSONHandler[MongoRepr[T]] = ???
implicit def toValue[T](repr: MongoRepr[T]): T = repr.value
}
我有三个不同的 Scala 项目,其中一个仅包含在其他两个 Scala 项目中使用的模型(以下称为库)。这三个项目之一是一个 Play 网络应用程序,我想将库中的模型保存在 MongoDB 中(代码驻留在 Play 应用程序中)。
所以在我的库中我有以下模型:
case class MyUser(id: UUID = UUID.randomUUID(), timestamp: Instant = Instant.now, name: String)
要在我的 Play 应用程序中保留 MyUser
的实例,我通常会使用此模型:
case class MyUser(var _id: Option[BSONObjectID] = None,
id: UUID,
timestamp: Instant,
name: String,
var created: Option[DateTime] = None,
var updated: Option[DateTime] = None
) extends Temporal
现在我正在寻找一种方法将我的库 MyUser
变形为我的 Play 应用程序中相应的 class,所以我想我会做这样的事情:
case class MyUser(var _id: Option[BSONObjectID] = None,
var created: Option[DateTime] = None,
var updated: Option[DateTime] = None
) extends lib.MyUser with Temporal
但是,现在我得到以下错误:
case-to-case inheritance is prohibited. To overcome this limitation, use extractors to pattern match on non-leaf nodes.
我想过这样做:
case class MyUser(var _id: Option[BSONObjectID] = None,
user: lib.MyUser,
var created: Option[DateTime] = None,
var updated: Option[DateTime] = None
) extends Temporal
我怎样才能以正确的方式设计它?
首先,错误告诉你的是你不应该为扩展的 class 使用 case class
。它所说的关于提取器的事情基本上是,如果需要在父 class 上进行模式匹配(这是 case class
的 case
的来源,简单的模式匹配),你总是可以为它编写自定义提取器。
但是,这不适用于您的情况,因为您无法更改您的库公开 case class
es 的事实。
另一种解决方案是考虑到您的两个 classes 之间没有继承关系。实际上,一个是用户的表示,而另一个是包含用户的存储单元的表示。所以看起来你提出的解决方案毕竟有一些意义。
不过,无论是在序列化对象(在 Mongo 中)还是在 JVM class 中,都必须添加一个字段似乎很麻烦,您需要始终在其中执行 mongoUser.user
从 MyUser
...
lib.MyUser
一个优雅的(恕我直言)解决方案是保留这个字段 user: lib.MyUser
,但是让你的序列化把这个字段压平到 BSON 对象的顶层,这样一个序列化的 MyUser
看起来喜欢
{
_id: ObjectId(_),
id: 123456789abc-1234-1234-1234,
...
}
就好像你继承了lib.MyUser
的领域一样。
反序列化
现在,只要您想从 Mongo 中获取 lib.MyUser
,仅供阅读,您可以直接以这种格式反序列化它,忽略添加的字段。但是,如果您需要对其进行一些更新,则必须将其反序列化为 MyUser
,以便能够更新此特定的 BSON 文档。
scala 用法
当您将对象反序列化为 MyUser
(例如,为了更新)时,您可能仍然希望轻松访问 lib.MyUser
中公开的所有字段,而无需访问该字段user
每次。这可以通过隐式转换来完成。
泛化
顺便说一下,您可以对任何您想要序列化的对象以通用方式执行此操作...
总结一下
case class MongoRepr[T](_id: Option[BSONObjectId]
value: T,
created: Option[DateTime],
updated: Option[DateTime])
object MongoRepr {
//implement flattened (de)serialization, this depends on your
// mongo driver and what you're using for serialization (JSON/BSON)
def handler[T](implicit handlerT: BSONHandler[T]): BSONHandler[MongoRepr[T]] = ???
implicit def toValue[T](repr: MongoRepr[T]): T = repr.value
}