2

I have

trait Builder[T, K] {
    def build(brick: T) : K

For that trait, i have multiple implementations...

class StringBuilder extends Builder[Foo, String] { ... }
class HouseBuilder  extends Builder[Baa, House]  { ... }
class BaaBuilder    extends Builder[Baz, Int]    { ... }

Depending on a given Type, i would like to choose from one implementation. Something like this (Pseudo-code):

class BuildingComponent @Inject()(builder: Set[Builder]){
    def doIt(item: Any) = {
       item match {
          case _: Foo => builder.filter(Foo).build(item)
          case _: Baa => builder.filter(Baa).build(item)
          case _: Baz => builder.filter(Baz).build(item)
    }
}

So 2 point:

  1. how could i inject all implementations of the trait "Builder"?? I found a bunch of questions that go in the same direction (using multibinder, TypeLiteral etc. but none of them facing the problem of injecting all implementations. Its just about "how to inject a specific implementation") I know how to bind multiple instances using multibinder; but not if it is a generic class...

  2. In the end i would like to use kind of facade-pattern. having one "builder" to inject, that gets all implementations injected and knows what builder is needed (see match-case-fracment above). But instead of using match-case, i had an eye on the MapBinder. Something like binding the Builder-implementations to a Map, that uses the class as a key.

e.g. (Pseudo-code)

class BuildingComponent @Inject()(builder: Map[Class,Builder]){
  def doIt(item: Any) = {
     builder.get(item.class).build(item)
  }
}
Th 00 mÄ s
  • 3,776
  • 1
  • 27
  • 46

1 Answers1

2

Guice initialize only classes it knows about. So you can inject all implementations into your facade and order them as you want. You will need to change this facade each time you add new implementation.. So not that good..

Alternative

To inform guice about your implementations dynamically you need some reflection. You can either use standard scala if you can have your Builder as sealed trait (example of getting all subclasses you can find here) or with thirdparty library (e.g. reflections).

I will explain last case

You will need imports in your build.sbt:

libraryDependencies ++= Seq(
  "com.google.inject.extensions" % "guice-multibindings" % "<your guice version>",
  "org.reflections" % "reflections" % "0.9.11")

You will need to create module and notify guice about it, e.g. in case of play framework you will need to put in application.conf

play.modules.enabled += "com.example.MyModule"

Now, I assume that you are able to put all your implementations into same package (you can check docs how to get all implementations in other cases). Let's say it is com.example.builders. Also in my example I assume that you are able to move type parameters T and K into type aliases (with generics it will be a bit more tricks with binding and injecting - you can try to find this way yourself). And your builder will be:

trait Builder {
  type T
  type K
  def build(brick: T) : K
}

Now to your module MyModule:

package com.example

import com.google.inject.AbstractModule
import com.google.inject.multibindings.Multibinder
import org.reflections.Reflections


class MyModule extends AbstractModule {
  override def configure(): Unit = {
    import scala.collection.JavaConverters._
    val r = new Reflections("com.example.builders")
    val subtypes = r.getSubTypesOf(classOf[Builder])

    val executorBinder = Multibinder.newSetBinder(binder(), classOf[Builder])
    subtypes.asScala.foreach {clazz =>
      executorBinder.addBinding().to(clazz)
    }
  }
}

Finally, you can inject builders: java.util.Set[Builder] where you need it.

Update (added example how to handle typed implementation)

As example: you will need additional class to keep your brick type info. I used abstract class as soon as Builder is trait it should be ok

import scala.reflect.runtime.universe._

trait Builder[T, K] {
  def build(brick: T): K
}

abstract class TypeChecker[T: TypeTag] {
  this: Builder[T, _] =>

  def isDefinedAt[C: TypeTag](t: C) = {
      typeOf[C] =:= typeOf[T]
  }
}

class Foo
class Baa
class House

// first builder implementation
class StringBuilder
  extends TypeChecker[Foo]
    with Builder[Foo, String] {
  override def build(brick: Foo) = {
    println("StringBuilder")
    ""
  }
}
// second builder implementation
class HouseBuilder
  extends TypeChecker[Baa]
    with Builder[Baa, House] {
  override def build(brick: Baa) = {
    println("HouseBuilder")
    new House
  }
}

// our set of builders
val s: Set[Builder[_, _] with TypeChecker[_]] = Set(
  new StringBuilder,
  new HouseBuilder
)


// here we check and apply arrived brick on our set of builders
def check[T: TypeTag](t: T) =
  s.filter(_.isDefinedAt(t)).
    foreach {b => b.asInstanceOf[Builder[T, _]].build(t)}


check(new Foo)
check(new Baa)
Evgeny
  • 1,760
  • 10
  • 11
  • Thanks for your fast anwser. To summarize: guice is not able to do what i want. Alternative is to do things on my own, use reflection, and limit myself a bit e.g. putting everything in the same package, using sealed traits etc. So if the builder trait comes out of a library and is not editable, im lost...?! – callidusTaurus Mar 22 '18 at 07:52
  • I described case I implemented before. I had trait under my control and could change it to simplify my way. I just did not want to investigate the way with type parameters, but it is possible (learn about `TypeLiteral`, e.g. answer: https://stackoverflow.com/questions/24657127/guice-injecting-generic-type). Using same package - again, I just simplified for myself, but using `reflections` lib you can find subtypes not limiting yourself by one package (see about scanners). So as result - you are not limited by `Builder` itself, just use my solution not directly, but adopt it to your case. – Evgeny Mar 22 '18 at 08:03
  • Ok, so i tried your solution and seems to work so far with one exception: using the generics as type-aliases instead, seems not to work. to be more concrete: how to identify the proper implementation? E.g. i got two implementations of builder. one overrides the types with Foo and Baa and the other one with Bar and Baz. In my component where i inject the Set[Builder] i got the type-information 'Foo' and 'Baa'. now is the question, how to filter this Set with the given type-info to get to correct implementation? – callidusTaurus Mar 22 '18 at 10:21