2013-04-16 7 views
26

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

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

class A 
{ 
    private DebugLogger logger; 

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

    // Additional methods of A follow, etc. 
} 

До сих пор это имеет смысл. Тем не менее, должна быть построена по другому классу B. Несколько экземпляров должны быть построены, так следующим образом кинжала делать вещи, я просто впрыснуть 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 
     ... 
    } 
} 

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

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

ответ

49

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

Вы можете обойти это с рисунком фабрики:

class AFactory { 
    @Inject DebugLogger debuggLogger; 

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

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

class B { 
    @Inject AFactory aFactory; 

    //... 
} 

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

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

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

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

+0

Благодарим за быстрый ответ. Заводская модель, которую вы описали, кажется наилучшим подходом. Я также понял, что если бы Кинжал поддерживал частную полевую инъекцию, это легко разрешило бы то, что я пытаюсь выполнить. Интересно, почему это не было частью оригинального дизайна Даггера? Я предполагаю, что Кинжал вводит рефлексию. Если это так, частные поля не должны вызывать проблем, верно? – Alex

+0

Кинжал возвращается к отражению, но это не является его основным средством инъекции. Он генерирует код, который непосредственно устанавливает поля или вызывает ваши конструкторы. По сути, он работает как любой другой фрагмент кода в исходном дереве и не может получить доступ к «частным» членам. –

+0

И некоторые менеджеры по безопасности нарушат размышления, которые зависят от меняющейся доступности в ваших классах. –

3

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

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

+0

Да, не беспокойтесь, я понимаю, что Кинжал все еще в ранних версиях. Мне нравится, что некоторые из этих других функций разрабатываются как плагины, чтобы сохранить общий размер двоичного файла (что для меня, по крайней мере, делает его таким привлекательным вариантом для разработки Android). – Alex

+1

Так называемая генерация автозавода: https://github.com/google/auto/tree/master/factory – phazei

3

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

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

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

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

Newables являются тип значения и объекты, такие как Creditcard, песни и т.д.

0

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

Во-первых, создать класс A с @AutoFactory аннотацию и @Provided аннотацию для инъекций аргументы:

@AutoFactory 
public class A { 

    private DebugLogger logger; 

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

Затем библиотека создает AFactory класс во время компиляции. Поэтому вам нужно просто ввести фабрику в конструктор класса 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); 
    } 
} 
Смежные вопросы