Mathias Lavaert
Mathias Lavaert

Reputation: 315

How to create a factory for a parameterized class?

I'm trying to build a factory for implementations of a generic trait.

Given my domain model:

trait Person

case class Man(firstName: String, lastName: String) extends Person  
case class Woman(firstName: String, lastName: String) extends Person

I created a repository for those classes like this:

trait Repository[T <: Person] {
  def findAll(): List[T]
}

class ManRepository extends Repository[Man] {
  override def findAll(): List[Man] = {
    List(
      Man("Oliver", "Smith"),
      Man("Jack", "Russel")
    )
  }
}

class WomanRepository extends Repository[Woman] {
  override def findAll(): List[Woman] = {
    List(
      Woman("Scarlet", "Johnson"),
      Woman("Olivia", "Calme")
    )
   }
 }

So far so good, some pretty simple classes. But I'd wanted to create a factory to create an instance of these repositories depending on some parameters.

object RepositoryFactory {
  def create[T <: Person](gender: String): Repository[T] = {
    gender match {
      case "man" => new ManRepository()
      case "woman" => new WomanRepository()
    }
  }
}

But this last piece won't compile. If I ommit the explicit return type of the factory, it compiles but returns a repository of type Repository[_1] instead of Repository[Man]

I can't seem to find a proper solution, do any of you guys have got some tips for me?

Upvotes: 1

Views: 150

Answers (2)

Chris B
Chris B

Reputation: 9259

How about this?

  • Instead of using a string, use a sealed trait that knows how to create the appropriate repo
  • Use dependent types to return the correct type of repository

Code:

object GenderStuff {

  trait Person
  case class Man(firstName: String, lastName: String) extends Person
  case class Woman(firstName: String, lastName: String) extends Person

  // Note that Repository has to be covariant in P.
  // See http://blogs.atlassian.com/2013/01/covariance-and-contravariance-in-scala/ for explanation of covariance.
  trait Repository[+P <: Person] {
    def findAll(): List[P]
  }

  class ManRepository extends Repository[Man] {
    override def findAll(): List[Man] = {
      List(
        Man("Oliver", "Smith"),
        Man("Jack", "Russel")
      )
    }
  }

  class WomanRepository extends Repository[Woman] {
    override def findAll(): List[Woman] = {
      List(
        Woman("Scarlet", "Johnson"),
        Woman("Olivia", "Calme")
      )
    }
  }

  sealed trait Gender {
    type P <: Person
    def repo: Repository[P]
  }
  case object Male extends Gender {
    type P = Man
    def repo = new ManRepository()
  }
  case object Female extends Gender {
    type P = Woman
    def repo = new WomanRepository()
  }

  object RepositoryFactory {
    def create(gender: Gender): Repository[gender.P] = gender.repo

    // or if you prefer you can write it like this 
    //def create[G <: Gender](gender: G): Repository[G#P] = gender.repo
  }

  val manRepo: Repository[Man] = RepositoryFactory.create(Male)
  val womanRepo: Repository[Woman] = RepositoryFactory.create(Female)
}

Of course, this makes the factory object pretty pointless - it's easier to just call Male.repo directly :)

Upvotes: 1

Joe Pallas
Joe Pallas

Reputation: 2155

You should probably think some more about what you're trying to achieve. In your example, what type would you expect to be returned by RepositoryFactory.create[Man]("woman")? The compiler has no way to associate those arbitrary strings with types.

Upvotes: 0

Related Questions