简体   繁体   中英

Scala factory for generic types using the apply method?

Suppose that I have the following trait that defines an interface and takes a couple of type parameters...

trait Foo[A, B] {

    // implementation details not important

}

I want to use the companion object as a factory for concrete implementations of the trait. I also want to force users to use the Foo interface instead of sub-classing So I hide the concrete implementations in the companion object like so:

object Foo {

  def apply[A, B](thing: Thing): Foo[A, B] = {
    ???
  }

  private case class FooImpl[A1, B1](thing: Thing) extends Foo[A1, B1]

  private case class AnotherFooImpl[A2, B1](thing: Thing) extends Foo[A2, B1]

}

I want to be able to use the factory as follows:

val foo = Foo[A1, B1](thing)  // should be an instance of FooImpl

val anotherFoo = Foo[A2, B1](thing)  // should be an instance of AnotherFooImpl

How do I implement the apply method to make this happen? This SO post seems close to the mark.

How about:

trait Foo[A, B]
trait Factory[A, B] {
  def make(thing: Thing): Foo[A, B]
}

class Thing

object Foo {
def apply[A, B](thing: Thing)(implicit ev: Factory[A, B]) = ev.make(thing)

private case class FooImpl[A, B](thing: Thing) extends Foo[A, B]
private case class AnotherFooImpl[A, B](thing: Thing) extends Foo[A, B]

implicit val fooImplFactory: Factory[Int, String] = new Factory[Int, String] {
  override def make(thing: Thing): Foo[Int, String] = new FooImpl[Int, String](thing)
}

implicit val anotherFooImplFactory: Factory[String, String] = new Factory[String, String] {
  override def make(thing: Thing): Foo[String, String] = new AnotherFooImpl[String, String](thing)
}

And now:

def main(args: Array[String]): Unit = {
  import Foo._

  val fooImpl = Foo[Int, String](new Thing)
  val anotherFooImpl = Foo[String, String](new Thing)

  println(fooImpl)
  println(anotherFooImpl)
}

Yields:

FooImpl(testing.X$Thing@4678c730)
AnotherFooImpl(testing.X$Thing@c038203)

Using TypeTags (to overcome erasure of type parameters), we can call the respective hidden implementations based on the type parameters passed in to the apply method like below. It correctly instantiates the respective implementations but the type information for Foo is lost, in fact its coming some garbage like _202 etc? I don't know why that is happening and how to retain the correct types for Foo. Maybe someone can throw light on this.

trait Foo[A,B]
object Foo {
   def apply[A: TypeTag, B: TypeTag](thing: Thing) = 
    if(typeTag[A] == typeTag[Int])  
      FooImpl(thing) 
    else if(typeTag[A] == typeTag[String]) 
      AnotherFooImpl(thing) 
    else 
      new Foo[Double,Double] {}

   private case class FooImpl(thing: Thing) extends Foo[Int, String]
   private case class AnotherFooImpl(thing: Thing) extends Foo[String, String]
  } 

Foo[Int,String](new Thing) // Foo[_202, _203] = FooImpl($sess.cmd123$Thing@50350b75)

The actual types for _203 and _203 are: ???
// type _203 >: String with _201, type _202 >: Int with _200 


Foo[String,String](new Thing) //Foo[_202, _203] = AnotherFooImpl($sess.cmd123$Thing@51d80d6)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM