Как использовать Guice AssistedInject с несколькими реализациями интерфейса?

У меня проблемы с поиском, как иметь "динамический AssistedInject". Под этим я подразумеваю, что я хотел бы предоставить фабрике имя реализующего класса, которое необходимо использовать во время выполнения, на основе параметра. Вот что у меня сейчас есть:

interface Letter {}

abstract class ALetter implements Letter {
    public ALetter(T1 t1, T2 t2) {...} 
}

class A implements Letter {
    public static final NAME = "A";

    @Inject
    public A(@Assisted T1 t1, T2 t2) { super(t1, t2); }
}

class B implements Letter {
    public static final NAME = "B";

    @Inject
    public B(@Assisted T1 t1, T2 t2) { super(t1, t2); }
}

Я хотел бы иметь возможность загружать либо A, либо B в зависимости от их имени, учитывая, что у меня будет T1, но мне понадобится T2. Это может выглядеть примерно так:

class MyClass {
    @Inject
    public MyClass(RequestData data, MyConfig config, ILetterFactory letterFactory) {
        Letter letter = letterFactory.get(data.getLetterName(), config.getT2());
    }
}

И я бы настроил ILetterFactory, используя что-то вроде:

install(new FactoryModuleBuilder().build(ILetterFactory.class));

Но я знаю, что в настоящее время это не сработает, поскольку lettername не является реальным параметром моего конструктора, а Guice просто так не работает. Это должно дать вам представление о том, чего я хочу.

Единственное решение, которое я нашел в настоящее время, не использует Guice: у меня есть собственная фабрика, которая разрешает конструктор класса на основе имени (через Map<String, Constructor>) и звонки newInstance предоставляя ему соответствующие параметры (вообще никаких инъекций, кроме как на самой фабрике).

Есть ли способ использовать Guice, чтобы сам не создавать эту фабрику? Спасибо

1 ответ

Решение

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

public interface ILetterFactory {
  /** This assumes that T2 is a Guice-satisfied dependency,
    * and not meant to be @Assisted, so leave it out of the interface. */
  Letter get(String which);
}

Первый вариант - позволить Guice предоставлять ваши экземпляры с провайдером, что наиболее полезно, если в ваших письмах широко различаются депы, или если ваш список деппов длинный или часто меняется. Если A и B нужны вспомогательные зависимости, замените Provider<A> с A.Factory что вы связаны через FactoryModuleBuilder.

public class LetterFactory implements ILetterFactory {
  @Inject Provider<A> aProvider;
  @Inject Provider<B> bProvider;

  @Override public Letter get(String which) {
    if (A.NAME.equals(which)) {
      return aProvider.get();
    } else if (B.NAME.equals(which)) {
      return bProvider.get();
    } else {
      throw new IllegalArgumentException("Letter does not exist");
    }
  }
}

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

public class LetterFactory implements ILetterFactory {
  @Inject Provider<T2> t2Provider;

  @Override public Letter get(String which) {
    if (A.NAME.equals(which)) {
      return new A(t2Provider.get());
    } else if (B.NAME.equals(which)) {
      return new B(t2Provider.get());
    } else {
      throw new IllegalArgumentException("Letter does not exist");
    }
  }
}
Другие вопросы по тегам