Как заставить фабрики хорошо работать с Guice? - PullRequest
0 голосов
/ 19 апреля 2019

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

// WidgetA must be created before WidgetB, because of the side-effects
// on the container.
WidgetAFactory.make(container);
WidgetBFactory.make(container);

Другой случай, когда конструктор принимает смесь вводимых значений и значений времени выполнения. Вместо использования:

@Inject
WidgetC(
    Container,
    @WidgetCFont Font,
    @WidgetCColor Color,
    @Named("flag") String flag) {
  ...
}

Я использую:

@Inject
WidgetCFactory(
    @WidgetCFont Font font,
    @WidgetCColor Color color,
    @Named("flag") String flag) {
  ...
}

WidgetCFactory.make(Container container) {
 return new WidgetC(container, font, color, flag);
}

Но я сталкиваюсь с двумя ограничениями при использовании заводов:

  1. В моем первом примере мне также нужно, чтобы WidgetA был @Singleton, который понадобится другим конструкторам @Injected. Пока что мое решение - сохранить экземпляр, созданный мной при вызове фабрики, и @ предоставить его для использования другими. Есть ли способ вернуть контроль над этим синглтоном обратно без необходимости поддерживать этот экземпляр сам?

  2. Во втором моем примере управление введенными зависимостями - беспорядок: WidgetCFactory должен вызывать конструктор WidgetC с длинным списком введенных значений, который должен обновляться для каждого изменения в зависимостях без аннотации. чеки. Есть ли способ предоставить Guice параметр времени выполнения и разрешить ему работать с другими зависимостями?

Такое чувство, что в обоих случаях я мог бы использовать дочерний инжектор, которому было бы присвоено значение времени выполнения, и позволить Guice быть фабрикой:

public static class WidgetCFactory {
  private final Injector injector;

  @Inject
  public WidgetCFactory(Injector injector) {
    this.injector = injector;
  }

  public WidgetC make(Container container) {
    Injector childInjector = injector.createChildInjector(new AbstractModule() {
      @Override protected void configure() {
        bind(Container.class).toInstance(container);
      }
    });
    return childInjector.getInstance(WidgetC.class);
  }
}

Но я не нахожу много случаев, когда люди делают это. Это потому, что это слишком тяжело или за пределами хорошей практики внедрения зависимостей? Что было бы лучше?

Ответы [ 2 ]

4 голосов
/ 20 апреля 2019

Смешивание введенных и исполняемых значений означает, что вы должны изучить «вспомогательную инъекцию», которая позволяет объявлять определенные внедренные значения, которые будут предоставлены во время выполнения вызывающей стороной, и генерировать фабрику, которая будет отображать только те в качестве параметра. Начиная с https://github.com/google/guice/wiki/AssistedInject, вы захотите установить модуль для каждого типа, который будет обрабатываться таким образом, что-то вроде

// this goes in your existing Module.configure()
install(new FactoryModuleBuilder()
     // you can add more than one type here in this way
     .implement(WidgetC.class, WidgetC.class)
     .build(WidgetFactory.class));

//...

public interface WidgetFactory {
    // you can add more than one method here 
    WidgetC createWidgetC(Container container);
}

@AssistedInject
WidgetC(
    @Assisted Container,
    @WidgetCFont Font,
    @WidgetCColor Color,
    @Named("flag") String flag) {
  ...
}

Обратите особое внимание на изменение в конструкторе WidgetC, как на разные аннотации на конструкторе (поскольку на самом деле это не безопасно создавать с помощью обычной инъекции), так и на параметре Container (который будет предоставлен заводом-изготовителем). , а не контейнер IoC.


Чтобы сделать WidgetA синглтоном, вы можете либо украсить тип с помощью @Singleton, либо связать его с помощью configure() метода:

bind(WidgetA.class).in(Singleton.class);

Как написано, он будет лениво создан - он будет существовать только после первого запроса, но затем каждый раз, когда его запрашивают, он будет одним и тем же экземпляром и не будет создан с нуля.

2 голосов
/ 23 апреля 2019

Чтобы повторить Правильный ответ Колина , Assisted Injection - это путь, и Guice предлагает Assisted Injection (через отдельную зависимость / JAR) с 2.0. Вы можете прочитать больше о реализации Guice на странице Guiki wiki AssistedInject , но у меня не было бы ничего примера, кроме того, что написал Колин.

Одна альтернатива, которую вы можете рассмотреть, находится в AutoFactory , которая генерирует кодовые реализации для вас. (Это часть Google Auto, набора генераторов кода для Java, который создает реализации аннотаций, сервисы, объекты с неизменяемыми значениями и фабрики.) Это де-факто стандарт для Dagger, но он применим к любой среде JSR-330, включая Guice.


Относительно вашего вопроса № 1, я отклонюсь от Колина и скажу, что то, что вы ищете, по сути своей несколько опасно: если @Singleton объекты существуют на протяжении всего жизненного цикла вашего приложения, но ваша фабрика WidgetA принимает контейнер, тогда ваш WidgetA может существовать до того, как ваш контейнер будет готов, или он будет существовать после уничтожения вашего контейнера.

Если ваш контейнер WidgetA также @Singleton, то вы можете создать WidgetA без фабрики, и все идет хорошо: вы можете пропустить фабрику, связать контейнер, нормально связать WidgetA и ввести Provider<WidgetA> (доступно без дополнительная настройка), чтобы отложить создание WidgetA, пока вы не будете готовы.

Если ваш реальный запрос заключается в том, чтобы WidgetA существовал ровно столько, сколько существует Контейнер, но для того, чтобы WidgetA / B / C все использовали один и тот же Контейнер и WidgetA для этого времени, вы можете считать дочерним инжектором где вы связываете свой контейнер и виджеты. Таким образом, каждый Контейнер получает свой собственный WidgetA, каждая инъекция WidgetA является последовательной в этом контейнере, и вы избавляетесь от WidgetA, когда получаете новый Контейнер. Конечно, если ваш Контейнер становится доступным только после того, как ваш Инжектор работает, и после этого он остается постоянным, вы можете использовать этот дочерний инжектор в качестве основного инжектора и после этого запустить WidgetA.

Если ваш WidgetA зависит от контейнера, который не запускается как доступный, будьте осторожны: это может быть «инъекция, расширяющая область действия», потому что ваш контейнер будет жить как @Singleton в WidgetA, даже если он иначе быть мусором В лучшем случае это может быть утечка памяти и странные ошибки, когда в вашем приложении существует несколько Контейнеров. Вы можете использовать модуль с состоянием, как вы использовали, но в любом случае будьте очень осторожны.

...