Подтвердить что ты не робот

Использование кинжала для инъекций зависимостей на конструкторах

Итак, я в настоящее время перепроектировал приложение для Android для использования Dagger. Мое приложение большое и сложное, и я недавно натолкнулся на следующий сценарий:

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

class A
{
    private DebugLogger logger;

    @Inject
    public A(DebugLogger logger)
    {
        this.logger = logger;
    }

    // Additional methods of A follow, etc.
}

До сих пор это имеет смысл. Тем не менее, A необходимо построить другим классом B. Множество экземпляров A должно быть построено, поэтому, следуя кинжалу, я просто вставляю a Provider<A> в B:

class B
{
    private Provider<A> aFactory;

    @Inject
    public B(Provider<A> aFactory)
    {
        this.aFactory = aFactory;
    }
}

Хорошо, хорошо. Но подождите, неожиданно A нуждается в дополнительных входах, таких как целое число, называемое "сумма", которое имеет жизненно важное значение для его построения. Теперь мой конструктор для A должен выглядеть так:

@Inject
public A(DebugLogger logger, int amount)
{
...
}

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

Я мог бы реорганизовать A, добавив метод setAmount(), который, как ожидается, будет вызываться после конструктора. Это уродливо, однако, потому что это заставляет меня отложить построение А до тех пор, пока не будет заполнено "количество". Если бы у меня было два таких параметра: "сумма" и "частота", тогда у меня было бы два сеттера, что означало бы либо сложную проверку, чтобы гарантировать, что построение резюме возобновляется после вызова обоих сеттеров, или мне нужно будет добавить еще третий метод в микс, например:

(Somewhere in B):

A inst = aFactory.get();
inst.setAmount(5);
inst.setFrequency(7);
inst.doConstructionThatRequiresAmountAndFrequency();

Другой альтернативой является то, что я не использую инъекцию на основе конструктора и не использую инъекцию на основе полей. Но теперь я должен сделать свои поля общедоступными. Это не очень хорошо со мной, потому что теперь я обязан раскрыть внутренние данные моих классов другим классам.

До сих пор единственным элегантным решением, которое я могу придумать, является использование полевой инъекции для поставщиков, например:

class A
{
    @Inject
    public Provider<DebugLogger> loggerProvider;
    private DebugLogger logger;

    public A(int amount, int frequency)
    {
        logger = loggerProvider.get();
        // Do fancy things with amount and frequency here
        ...
    }
}

Я все еще не уверен в сроках, так как я не уверен, что Кинжал будет вводить провайдера перед вызовом моего конструктора.

Есть ли лучший способ? Я просто что-то пропустил о том, как работает Кинжал?

4b9b3361

Ответ 1

То, о чем вы говорите, называется вспомогательной инъекцией и в настоящее время не поддерживается кинжалом любым способом.

Вы можете обойти это с помощью шаблона factory:

class AFactory {
  @Inject DebugLogger debuggLogger;

  public A create(int amount, int frequency) {
    return new A(debuggLogger, amount);
  }
}

Теперь вы можете ввести этот factory и использовать его для создания экземпляров A:

class B {
  @Inject AFactory aFactory;

  //...
}

и когда вам нужно создать A с вашей "суммой" и "частотой", вы используете factory.

A a = aFactory.create(amount, frequency);

Это позволяет A иметь final экземпляры полей журнала, количества и частоты, все еще используя инъекцию, чтобы предоставить экземпляр журнала.

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

Ответ 2

То, что пишет Джейк, совершенно верно. Тем не менее, мы (некоторые из людей Google, которые работают с Guice and Dagger) работают над альтернативной версией "вспомогательной инъекции" или автоматической генерации factory, которую следует использовать Guice или Dagger или автономно - это, он будет генерировать исходный код класса factory для вас. Эти классы factory будут (при необходимости) инъектированы, как и любой стандартный класс JSR-330. Но он еще не выпущен.

В ожидании такого решения целесообразно использовать подход Джейка Уортона.

Ответ 3

У вас возникла проблема, потому что вы смешиваете инъекционные и неинъекционные материалы в своем конструкторе. Общие правила инъекции, которые избавят вас от сердечной боли и сохранят ваш код:

  • Инъекции могут запрашивать другие инъекции в своем конструкторе, но не для новых.

  • Newables может запрашивать другие новинки в своем конструкторе, но не для инъекций.

Инъекции - это объекты типа обслуживания, то есть объекты, которые работают, такие как CreditCardProcessor, MusicPlayer и т.д.

Newables - это объекты типа значения, такие как CreditCard, Song и т.д.

Ответ 4

Сообщение Джейка замечательно, но есть более простой способ. Google создал AutoFactory для создания factory автоматически во время компиляции.

Сначала создайте класс A с аннотацией @AutoFactory и аннотацией @Provided для ввода аргументов:

@AutoFactory
public class A {

    private DebugLogger logger;

    public A(@Provided DebugLogger logger, int amount, int frequency) {
        this.logger = logger;
    }
}

Затем библиотека создает класс AFactory во время компиляции. Поэтому вам нужно просто вставить factory в конструктор класса B.

public class B {

    private final AFactory aFactory;

    @Inject
    public B(AFactory aFactory) {
        this.aFactory = aFactory;
    }

    public A createA(int amount, int frequency) {
        return aFactory.create(amount, frequency);
    }
}