1

I know that we can overload class constructor in Scala as follows-

class Foo(x: Int, z: String) { 
  def this(z: String) = this(0, z);   
}

But how can I overload a class which have two completely different types of parameters as below (imagine that I can identify an user either by name or numeric id)

class User(userId: Int) {
  ...
}

class User(userName: String) {
  ...
}
1
  • There's no real difference between the two cases; neither have overload conflicts. I'm not sure what is not doing what you want or expect or, really, what exact problem you're encountering. Commented Apr 15, 2013 at 15:08

3 Answers 3

3

(imagine that I can identify an user either by name or numeric id)

You almost certainly don't want to do this by having optional fields in your class. Rather, you should encode the fact that the user is identified in various ways into the types and structure of your program.

One way to do this is to encode the user identifier using Scala's built-in Either type:

class User private(identifier : Either[String, Int]) {
  def this(id : Int) = this(Right(id))
  def this(name : String) = this(Left(name))
}

However, you might also want to make the nature of the user identifier a little more explicit, and encode it as your own Algebraic data type:

trait UserIdentifier
object UserIdentifier {
  case class ById(id : Int) extends UserIdentifier
  case class ByName(name : String) extends UserIdentifier
}

class User(id : UserIdentifier) {
  def this(id : Int) = this(UserIdentifier.ById(id))
  def this(name : String) = this(UserIdentifier.ByName(name))
}

By doing it this way, you prevent problems such as somebody trying to look up a name on a User which is identified by an id instead. The second approach also allows you to extend the idea of a UserIdentifier in the future, in case a user can be identified by some other construct.

Sign up to request clarification or add additional context in comments.

1 Comment

thanks a lot for taking time to respond. The problem with the first approach it confine the solution for TWO types, cannot be expanded for more types. But I definitely vote for the second approach.
2

Alternativelly, you can do this

object User {
  def apply(userId: Int) = new UserI(userId)
  def apply(name: String) = new UserS(name)

  class UserI(userId: Int)
  class UserS(userName: String)
}

And use it this way:

  val u1 = User(1)
  val u2 = User("a")

If you have a lot of common logic you can put it into a common abstract class

object User {
  def apply(userId: Int) = new UserI(userId)
  def apply(name: String) = new UserS(name)


  class UserI(userId: Int) extends AUser
  class UserS(userName: String) extends AUser

  abstract class AUser{
    // common logic for both classes
  }

}

Comments

1

You can do this:

class User private() {
  def this( userName: String ) = { this(); ??? }
  def this( userId: Int ) = { this(); ??? }
}

the private keyword makes the no-arg constructor private. This means your other secondary constructors don't need to pass anything to the primary constructor (effectively making the two secondary constructors independant), but the callers still cannot instantiate the class withouth passing any parameter. Note that this pattern can be tricky to use when your class has vals to initialize from the construtors parameters.

1 Comment

Excellent trick! It worked well. What if User is a case class?

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.