Как стать автором
Обновить

Комментарии 14

Правильно ли я понимаю, что при вызове
Tweeter tweeter = new Tweeter();
будет каким-то образом вызван конструктор с параметром?
Не совсем. В случае с Dagger, мы убрали HttpClient из конструктора, поэтому у класса Tweeter остался только конструктор по умолчанию. Т.е. HttpClient указывается только в классе, которому он непосредственно необходим и нет необходимости «тянуть» его из родительских классов. Забыл добавить этот класс в вариант с Dagger — обновлю пост. Спасибо!
Получается, что реальное значение client в TwitterApi получит при вызове
Injector.inject(this);
в конструкторе?
А что тогда значит this.client = client;?
Все верно, client инициализируется при inject'e. this.client = client — досадный копи-паст… Поправил. Спасибо еще раз!
Вот это:
    public TwitterApi()
    {
        //Добавляем класс в граф зависимостей
        Injector.inject(this);
        //На этом этапе "магическим" образом client проинициализирован Dagger'ом
    }

очень нехорошо. Убирая зависимость на классы бизнес-логики, вы добавляете зависимость на класс инжектора. TwitterApi невозможно будет использовать отдельно от контейнера DI.

Самым правильным способом, имхо, является использование внедрения через конструктор. Guice умеет так (да и Dagger, я думаю, тоже):

public class TwitterApi {
    private final HttpClient client;
    
    @Inject
    public TwitterApi(HttpClient client) {
        this.client = client;
    }

    ...
}

и пусть теперь этот класс создаётся контейнером, когда потребуется его использование внутри проекта. Если же его нужно протестировать, в модульном тесте вы делаете просто new TwitterApi(mockClient), без всяких контейнеров. Использование контейнеров в модульных тестах, кстати, есть плохой тон.
Не очень хорошо понимаю философию DI, поэтому, возможно, глупый вопрос. Как быть, если в некотором месте мне нужно насоздавать произвольное количество объектов, классом которых я хочу управлять с помощью DI-контейнера? Ну, в данном случае пусть мне надо создать массив HttpClient'ов. И как быть, если эти объекты сами имеют внедряемые зависимости?
Без проблем. Внедрите не сам объект, а его провайдер (в терминологии Guice, в Spring, кажется, это называется Factory):
public class TwitterApi {
    private final Provider<HttpClient> clientProvider;
    
    @Inject
    public TwitterApi(Provider<HttpClient> clientProvider) {
        this.clientProvider = clientProvider;
    }

    ...
}

и теперь можно использовать этот провайдер, чтобы насоздавать столько объектов, сколько нужно:
List<HttpClient> httpClients = new ArrayList<>();
for (int i = 0; i < 10; ++i) {
    httpClients.add(clientProvider.get());
}


Все зависимости при этом будут разрешены автоматически. Тут ещё от scope'а привязки зависит. Если HttpClient помечен как singleton, то контейнер сам позаботится, чтобы существовал только один экземпляр объекта, и провайдер всегда будет возвращать его. Если scope не указан, объекты будут создаваться на каждый вызов get().

Есть и другие паттерны создания и внедрения объектов. Например, Guice позволяет создавать объекты таким образом, чтобы часть зависимостей объекта внедрялась, а часть передавалась из кода бизнес-логики (расширение AssistedInject). Можно делать провайдеры, которые могут выбрасывать исключения, чтобы затем обрабатывать эти исключения в коде бизнес-логики (расширение ThrowingProviders). Можно сделать привязки на множество реализаций одного интерфейса и внедрить их все сразу как Set или Map, что бывает полезно для плагинной архитектуры (расширение Multibindings). Не знаю, как с этим у Dagger, но Guice умеет всё вышеперечисленное, и это очень удобно.
Спасибо.
Интересно, в спринге вроде бы по умолчанию как раз синглтон-скоуп…
Надо посмотреть наконец на этот Guice, а то все спринг да спринг :)
Да, в спринге по умолчанию все бины — синглтоны. Ну это в некотором роде соответствует ментальной модели спринговой XML-конфигурации, когда вы описываете компоненты-бины. В Guice нет понятия бинов, там привязки реализаций к интерфейсам, и там создание новых копий объектов при внедрении более логично.
Ну попробую со своей колокольни ответить, не претендую на 100% правильность. В спринге (с даггером не работал, не знаю) можно задать bean scope = prototype. И при каждом инжекте этого бина вы будете получать новый инстанс. Насколько я понял, вам это нужно? Вообще есть много всяких скоупов.
Все верно — в данном примере я показал Fields Injection. Есть еще Constructor Injection, о котором Вы говорите.
На самом деле, Injector.inject() рано или поздно прийдется сделать, потому что для кого-то TwitterApi будет полем, а при FieldInjection необходимо добавлять себя в контейнер.
Тем не менее, Ваш вариант явно лучше хотя бы тем, что TwitterApi модуль уже не зависит от контейнера. Опыта с DI у меня немного, поэтому на подобные грабли не наступал (пока :) )
Injector.inject() рано или поздно прийдется сделать, потому что для кого-то TwitterApi будет полем, а при FieldInjection необходимо добавлять себя в контейнер.

Нет, не придётся. Нужно просто не делать TwitterApi внедряемым полем, и всё. Вы же контролируете ваш код, не так ли? :)
Опыта с DI у меня немного, поэтому на подобные грабли не наступал (пока :) )

Всё ещё впереди) я начинал со спринга, и очень долго не мог понять, что такое DI вообще и зачем нужен спринг в частности. Потом щёлкнуло, и сразу всё стало понятно)
хорошая презентация по DI от Jake Wharton тут
Зарегистрируйтесь на Хабре , чтобы оставить комментарий

Публикации

Истории