2017-04-13 127 views
1

我们使用大小写类来表示在客户端和服务器之间传输的JSON对象。除了我们已经生活了很长一段时间以外,它一直在努力工作,我想知道是否有人有一个聪明的方法。这可能会减少代码重复

比方说,我有一个用户对象具有ID,名字,姓氏和电子邮件地址。一旦用户被保存到数据库中,他有一个分配给他的id(Int),因此对于处理现有用户的客户端和服务器之间的所有通信,该id是必填字段。事实上,只有一种情况是不需要id字段,那是用户第一次被保存的情况。我们目前正在处理这一问题的方法是用一个案例类,看起来像这样:

case class User(id: Option[Int], firstName: String, lastName: String, email:String) 

在所有情况下,除了最初的保存,该ID是Some并为初始保存ID始终None所以我们发现自己使用id.getOrElse(0)经常。 (有时候,我们会做一个.get但感觉很脏。)

我很想有是有id: Int领域的现有用户和没有id字段的对象在所有的新用户对象,但不宣所有其他领域在两个单独的案例分类中两次。但是,我看不到方便的方法。我也不喜欢为新用户的id字段使用'魔术'数字。

有没有人有更好的解决这个问题?

+0

的方法之一是使用继承,即类用户没有ID,但InitializedUser从用户扩展添加这一个领域 –

+0

同样的问题存在于数据库中。请查找Rob Norris的演讲“固定点类型的纯数据库编程”。 – ashawley

+0

@Lashane我不认为这有帮助。子类必须重新声明构造函数的其他字段才能填充超类。除非我错过了一些东西。 –

回答

1
case class User[+IdOpt <: Option[Int]](idOpt: IdOpt, firstName: String, lastName: String, email:String) 
object User { 
    // Type aliases for convenience and code readability 
    type New = User[None.type] 
    type Saved = User[Some[Int]] 
    type Value = User[Option[Int]] // New or Saved 

    implicit class SavedOps(val user: Saved) extends AnyVal { 
    def id: Int = user.idOpt.get 
    } 
} 

测试:

scala> val billNew = User(None, "Bill", "Gate", "[email protected]") 
billNew: User[None.type] = User(None,Bill,Gate,[email protected]) 

scala> billNew.id 
<console>:17: error: value id is not a member of User[None.type] 
     billNew.id 
      ^

scala> val billSaved = billNew.copy(idOpt = Some(1)) 
billSaved: User[Some[Int]] = User(Some(1),Bill,Gate,[email protected]) 

scala> billSaved.id 
res1: Int = 1 
+0

这很有趣,给了我一些想法。这并不完全符合我的想法,但是因为这是我得到想法的地方,所以我会接受它作为答案,并将我作为一个单独答案发布。 –

0

如果你将它从用户身上隐藏起来,拥有一个幻数并不是一个可怕的想法。实际上它是一种常见的模式,Slick使用它。您可以忽略要插入的对象的id值。

所以,你可以通过使构造包私人

case class User private[db](id: Int, firstName: String, lastName: String, email:String) 

开始,然后提供一个同伴对象为用户没有ID创建

object User{ 
    def apply(firstName: String, lastName: String, email: String): User = User(-1, firstName, lastName, email) 
} 

现在你可以构建它,就好像ID不是必需的

val user = User("first","last","email") 
+1

我不喜欢魔术数字的事情是,我必须“知道”我正在处理的对象是否具有有效的ID,或者我必须检查它是否等于幻数。那时我已经失去了我的类型安全性,因为类型不告诉我它是新用户还是现有用户。诚然,我有一个可选的id相同的问题,但与可选的id编译器不会让我忘记了id的有效性。 –

1

这是我们结束了,现在去。

trait Resource[T <: Option[Int]] { 
    def idOpt: T 
} 

object Resource { 
    type IsSome = Some[Int] 
    implicit class SomeOps[R <: Resource[IsSome]](val resource: R) { 
    def id: Int = resource.idOpt.get 
    } 
} 

这使我们能够使用它像这样:

case class User[T <: Option[Int]](idOpt:T, firstName:String, lastName:String, email:String) extends Resource[T] 
case class Company[T <: Option[Int]](idOpt:T, companyName: String) extends Resource[T] 

val u1 = User(None, "Bubba", "Blue", "[email protected]") 
val u2 = User(Some(1), "Forrest", "Gump", "[email protected]") 
u1.id // <-- won't compile 
u2.id // <-- compiles