Внедрить все реализации определенной черты / класса в игру, используя 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. Как я мог внедрить все реализации черты "Строитель"?? Я нашел кучу вопросов, которые идут в одном направлении (с использованием multibinder, TypeLiteral и т. Д., Но ни один из них не сталкивался с проблемой внедрения всех реализаций. Это просто о том, "как внедрить конкретную реализацию"). Я знаю, как связать несколько экземпляров. использование мультибиндера; но не, если это универсальный класс...

  2. В конце я хотел бы использовать вид фасада. наличие одного "строителя" для внедрения, который вводит все реализации и знает, какой сборщик необходим (см. раздел соответствия случаев выше). Но вместо использования match-case я следил за MapBinder. Что-то вроде привязки реализаций Builder к карте, которая использует класс в качестве ключа.

например (псевдокод)

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

1 ответ

Guice инициализирует только те классы, о которых он знает. Таким образом, вы можете внедрить все реализации в ваш фасад и заказать их, как вы хотите. Вам нужно будет менять этот фасад каждый раз, когда вы добавляете новую реализацию. Так что не очень хорошо..

альтернатива

Чтобы динамически проинформировать Guice о ваших реализациях, вам нужно подумать. Вы можете использовать стандартную Scala, если вы можете иметь Builder как sealed trait (пример получения всех подклассов, которые вы можете найти здесь) или с библиотекой третьей стороны (например, отражения).

Я объясню последний случай

Вам понадобится импорт в вашем build.sbt:

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

Вам нужно будет создать модуль и уведомить об этом руководство, например, в случае игрового фреймворка вам нужно будет поместить в application.conf

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

Теперь я предполагаю, что вы можете поместить все свои реализации в один и тот же пакет (вы можете проверить документы, как получить все реализации в других случаях). Скажем так com.example.builders, Также в моем примере я предполагаю, что вы можете перемещать параметры типа T а также K в псевдонимы типов (с дженериками будет немного больше трюков с привязкой и внедрением - вы можете попытаться найти этот путь самостоятельно). И ваш строитель будет:

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

Наконец, вы можете ввести builders: java.util.Set[Builder] где вам это нужно.

Обновление (добавлен пример обработки типизированной реализации)

Например: вам понадобится дополнительный класс для хранения информации о типе вашего кирпича. я использовал abstract class как только 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)
Другие вопросы по тегам