scala - 使用 guice 注入(inject)游戏中某个特征/类的所有实现

标签 scala generics dependency-injection guice

我有

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

对于这个特征,我有多种实现......

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

根据给定的类型,我想从一种实现中进行选择。像这样的东西(伪代码):

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)
    }
}

所以2点:

  1. 我如何注入(inject)特征“Builder”的所有实现? 我发现了一堆朝着相同方向发展的问题(使用 multibinder、TypeLiteral 等,但没有一个面临注入(inject)所有实现的问题。它只是关于“如何注入(inject)特定实现”)我知道如何绑定(bind)多个实例使用多重绑定(bind)器;但如果它是一个泛型类则不然......

  2. 最后我想使用某种外观模式。有一个“构建器”要注入(inject),它会注入(inject)所有实现并知道需要什么构建器(请参见上面的 match-case-frament)。但我没有使用匹配大小写,而是关注了 MapBinder。类似于将 Builder 实现绑定(bind)到 Map,使用类作为键。

例如(伪代码)

class BuildingComponent @Inject()(builder: Map[Class,Builder]){
  def doIt(item: Any) = {
     builder.get(item.class).build(item)
  }
}

最佳答案

Guice 仅初始化它知道的类。因此,您可以将所有实现注入(inject)到外观中,并根据需要对它们进行排序。每次添加新的实现时,您都需要更改此外观..所以不太好..

替代方案

要动态地向 guice 通报您的实现,您需要一些反射(reflection)。如果您可以将 Builder 作为 sealed 特征(获取您可以找到的所有子类的示例 here )或使用第三方库(例如 reflections ),则可以使用标准 scala。

我将解释最后一个案例

您需要在 build.sbt 中导入:

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

您需要创建模块并通知 guice,例如如果是播放框架,您需要放入 application.conf

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

现在,我假设您能够将所有实现放入同一个包中(您可以检查文档如何在其他情况下获取所有实现)。假设它是com.example.builders。另外,在我的示例中,我假设您能够将类型参数 TK 移动到类型别名中(使用泛型,绑定(bind)和注入(inject)将有更多技巧 - 您可以尝试自己找到这个方法)。你的构建器将是:

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

现在到您的模块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)
    }
  }
}

最后,您可以在需要的地方注入(inject)builders: java.util.Set[Builder]

更新(添加了如何处理类型化实现的示例)

例如:您将需要额外的类来保存您的砖 block 类型信息。一旦 Builder 是特征,我就使用了抽象类,它应该没问题

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)

关于scala - 使用 guice 注入(inject)游戏中某个特征/类的所有实现,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/49409608/

相关文章:

scala - 忽略 Gatling 中的响应主体

android - 如何注入(inject)动态创建的用例(android、整洁的架构、dagger2)

php - Symfony:如何获取所有服务及其各自的类

dependency-injection - Ninject - 如何以及何时注入(inject)

scala - 使用 ChainBuilder 定义加特林用户流的最佳方法是什么?

java - 使用反射覆盖 2 + 2 = 5

scala - Scala 中的逆变和协方差

List 和 List<Object> 之间的 Java 区别

c# - 从字符串中检索类型

generics - 检查对象是否是 Kotlin 中的多种类型之一