Scala特质/蛋糕模式 vs case类

3
在我的Web应用程序中,授权用户至少具有4个“方面”:http会话相关数据、持久数据、Facebook数据和运行时业务数据。
我决定使用案例类组合而不是特质,原因至少有两个:
  • 特质混合可能会导致名称冲突
  • 我想要免费的案例类好处,如模式匹配和复制方法
我想知道有经验的Scala专家对此问题的意见。看起来特质和/或蛋糕模式应该适用于这些任务,但正如我上面所提到的,存在问题...很明显,我不仅想快速轻松地实现它,而且还想深入了解以备将来使用。
那么,我的决定是否有任何缺陷或误解,还是正确的?
相关代码如下:

case class FacebookUserInfo(name: String, friends: List[Long])
case class HttpUserInfo(sessionId: String, lastInteractionTime: Long, reconnect: Boolean)
case class RuntimeQuizUserInfo(recentScore: Int)
trait UserState {
  def db: User
  def http: HttpUserInfo
}

case class ConnectingUser(db: User, http: HttpUserInfo) extends UserState
case class DisconnectedUser(db: User, http: HttpUserInfo, facebook: Option[FacebookUserInfo]) extends UserState
case class AuthorizedUser(db: User, http: HttpUserInfo, facebook: FacebookUserInfo,
                          quiz: RuntimeQuizUserInfo) extends UserState
2个回答

4
我认为答案很简单:只要一切都真正“属于”您的对象,只要一切都在同一个“问题域”中,就可以使用继承。
蛋糕模式的目的是将某些部分从对象中分离出来,这些部分在某种程度上是必需的,但并不真正属于对象本身,例如策略、装饰、配置、上下文等。记录日志是一个典型的例子。通常情况下,我们谈论的是您不想“硬编码”事物的情况,例如您会考虑在Java中使用DI框架(如Guice或Spring)的情况。请参见http://jonasboner.com/2008/10/06/real-world-scala-dependency-injection-di.html获取一个好的例子。
一个经常有助于决定该做什么的问题是:“我如何测试对象行为?”如果您发现难以设置适当的测试环境,那么您应该将事物解耦,这意味着使用DI,而蛋糕模式通常可以方便地实现它。

“Cake pattern”这个术语通常用于服务的上下文中,而不是数据。我将它添加到我的问题中,主要是因为“名称冲突”问题在所有种类的特质混合中都很常见。 - Oleg Galako
而且我不完全理解你所说的“使用继承”是什么意思?问题是什么更好:特质混合(两级模型)还是组合。我不喜欢继承(多级模型),因为它会使事情变得繁重。对我来说,轻量级接口总是更可取的,因为它们使所有上下文更简单(没有额外的数据需要关注)。 - Oleg Galako

2
第三个选择是使用隐式转换器,也被称为 "pimp my library",但这可能并不必要,因为您可以控制代码。
这完全取决于您希望对象的某些方面看起来有多透明。您可以假装它是一个普通的 case class 对于世界上的其他部分,但在内部通过使用隐式来使其执行额外的工作。使用 case class 来保存数据是合适的,但我也觉得根据她的身份验证状态使用三个类(ConnectingUser,DisconnectedUser,AuthenticatedUser)来表示相同的对象是很尴尬的。
对于 UserState,您可以提供一个提取器,以便它的行为像一个 case class:
object UserState {
  def unapply(state: UserState) = Some(state.db, state.http)
}

这可以在比赛状态下使用,如下所示:
val user = ConnectingUser(User(), HttpUserInfo("foo", 0, false))
user match {
  case UserState(db, http) => println(http)
}

谢谢你的回答。我目前在继续使用 case classes(因为修改混入 traits 创建的对象也存在问题,即 case classes 的复制方法所做的方式,因为您必须使用反射或隐式定义每个 trait 组合的构造方法)。因此,在我的情况下,将某些接口匹配到隐式转换可能会很有用。 - Oleg Galako

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接