Reputation: 4385
I'm working on modeling entities that will be persisted in a DB. Using a User entity as an example, I'd like to work with them in this way:
val userBeforePersisting = new User("Joe", "[email protected]")
// DB access code (where rs is a ResultSet)
val foundUser = new User(rs.getLong("id"), rs.getString("name"), rs.getString("email"))
I'd like to use the same User code (i.e. minimize code duplication), while having two types of users:
I'd like to enforce this as strictly as possible at compile-time.
I'd like to be able to treat all Users the same, except if I try and get an ID from an un-persisted User, an error will be raised or it would not compile.
I'd like to avoid having to make separate classes like this
class NewUser(val name: String, val email: String)
class PersistedUser(val id: Long, val name: String, val email: String)
I don't like this solution because of the code duplication (name and email fields).
Here's kind of what I'm thinking:
class User(val id: Long, val name: String, val email: String) {
this(name: String, email: String) = this(0l, name, email)
this(id: Long, name: String, email: String) = this(id, name, email)
}
But then my un-persisted users have an id
of 0l
.
Here's another approach:
trait User {
val name: String
val email: String
}
class NewUser(val name: String, val email: String) extends User
class PersistedUser(val id: Long, val name: String, val email: String) extends User
This gives me the compile-time checks that I'd like. I'm not sure if there are any draw-backs to this.
Maybe I could try something like this:
class User(val name: String, val email: String)
trait Persisted { val id: Long }
class PersistedUser(val id: Long, val name: String, val email: String)
extends User(name, email)
with Persisted
Any thoughts to these approaches? I've never done it this way, so I'm not sure if I understand all the consequences.
Upvotes: 1
Views: 303
Reputation: 16255
Sounds like a possible use of Option
.
class User(val id: Option[Long], val name: String, val email: String)
So persisted users have an id
of Some(id)
whereas non-persisted users have None
.
As a convenience, you could grant id
a default value of None
:
class User(val id: Option[Long] = None, val name: String, val email: String)
// When you have an id...
val foundUser = new User(Some(rs.getLong("id")),
name = rs.getString("name"), email = rs.getString("email"))
// When you don't
val userBeforePersisting = new User(name = "Joe", email = "[email protected]")
// However this will throw a runtime error:
val idThatDoesntExist: Long = userBeforePersisting.id.get
This should also work with your multi-constructor example:
class User(val id: Option[Long], val name: String, val email: String) {
def this(name: String, email: String) = this(None, name, email)
def this(id: Long, name: String, email: String) = this(Some(id), name, email)
}
I thought Option
might make sense because you'd like to express in the same class that a certain field can either have a value or not. The only other way seems to be to have two classes (possibly one inheriting from the other) with only one having an id
field.
Upvotes: 3