Цель Guice
Я (думаю, я) понимаю цель инъекции зависимостей, но я просто не понимаю, зачем мне нужно что-то вроде Guice, чтобы это сделать (ну, очевидно, мне не нужен Guice, но я имею в виду, почему это было бы полезно использовать его). Скажем, у меня есть существующий (не-Guice) код, что-то вроде этого:
public SomeBarFooerImplementation(Foo foo, Bar bar) {
this.foo = foo;
this.bar = bar;
}
public void fooThatBar() {
foo.fooify(bar);
}
И где-то более высокий уровень, возможно, в моей main(), у меня есть:
public static void main(String[] args) {
Foo foo = new SomeFooImplementation();
Bar bar = new SomeBarImplementation();
BarFooer barFooer = new SomeBarFooerImplementation(foo, bar);
barFooer.fooThatBar();
}
Теперь у меня в основном есть преимущества инъекции зависимостей, правильно? Легкость тестирования и т.д.? Конечно, если вы хотите, вы можете легко изменить main(), чтобы получить имена классов реализации из конфигурации вместо hardcoding.
Как я понимаю, сделать то же самое в Guice, я бы сделал что-то вроде:
public SomeModule extends AbstractModule {
@Override
protected void configure() {
bind(Foo.class).to(SomeFooImplementation.class);
bind(Bar.class).to(SomeBarImplementation.class);
bind(BarFooer.class).to(SomeBarFooerImplementation.class);
}
}
@Inject
public SomeBarFooerImplementation(Foo foo, Bar, bar) {
this.foo = foo;
this.bar = bar;
}
public static void main(String[] args) {
Injector injector = Guice.createInjector(new SomeModule());
Foo foo = injector.getInstance(Foo.class);
barFooer.fooThatBar();
}
Это так? Мне кажется, что это просто синтаксический сахар, а не особенно полезный синтаксический сахар. И если есть преимущество в том, чтобы разбить "новый XxxImplementation()" материал на отдельный модуль, а не делать это непосредственно в main(), что легко сделать без Guice в любом случае.
У меня такое ощущение, что я пропустил что-то очень основное. Не могли бы вы объяснить мне, как выгодно использовать Guice?
Спасибо заранее.
Ответы
Ответ 1
В реальной жизни и больших приложениях уровень компонентов не равен 2 (как в вашем примере). Это может быть 10 или более. Также требования и зависимости могут измениться по прихоти.
При выполнении "DI вручную" это означает, что вы должны вручную изменить все пути ко всем компонентам и их транзитивным зависимостям. Это может быть совсем немного работы.
С "реальным DI" (т.е. Guice, Spring, CDI) вы просто меняете один или два затронутых компонента и проводку (Module
на Guice) и что это.
Также: представьте, что некоторые из ваших глубоко вложенных компонентов находятся на фабриках, которые должны быть вызваны другими компонентами. В Guice вы просто вводите Provider<Foo>
и это. Когда вы делаете это вручную, вы должны перетаскивать зависимости фабрик и их экземпляров в вашем коде.
Это еще более неприятно.
Изменить. Чтобы прояснить последнюю точку: изображение у вас есть глубоко вложенная логика (10 или более уровней). Внизу ваша логика должна создать factory на основе некоторого значения, например. фактическая температура: если "теплый", тогда требуется Cooler
, если "холодно", то a Heater
. Оба имеют интерфейс TemperatureAdjustor
. Для Cooler
и Heater
требуется много разных зависимостей, чтобы выполнить свою работу.
Поскольку тип экземпляра фактора нельзя создать в main
и передать его туда, где он нужен. Таким образом, вы в конечном итоге передаете зависимости обоих заводов до места, где они необходимы. ( "много зависимостей" плюс "много зависимостей" "слишком много, чтобы оставаться в здравом уме" )
С помощью "реального DI", такого как Guice, вы можете использовать AssistedInject
, чтобы избежать беспорядка зависимостей и дать только фактическое значение для бизнеса (здесь: температура) для factory и сделать. Проводка factory выполняется в модуле.
Ответ 2
Диск с ручной кодировкой - это реальный DI.
Когда вы рассматриваете сложность контейнера IOC, вам нужно глубже изучить, почему вы его используете. Я был анти-МОК в течение долгого времени, имея возможность проектировать и внедрять чистый, быстрый, аккуратный ручной DI-код. Но я смирился с идеей контейнеров, потому что они помогают стандартизировать реализацию и предоставляют более основанный на конфигах механизм для добавления новых решений в уравнение зависимости. И кто-то еще написал их для вас, поэтому вам не нужно тратить время на ручную кодировку.
Многие говорят, что использовать контейнеры в больших приложениях и ручной код в маленьких. Я говорю об обратном. Маленькие приложения не настолько ограничены производительностью. Пойдите для скорости выполнения использования упакованного контейнера. В больших приложениях, где вы будете тратить много времени на оптимизацию, подумайте о ручном кодировании вашего DI.
edit: но позвольте мне добавить, что контейнер IOC или ручной код всегда используют DI и код для интерфейса - независимо от того, насколько просты приложения, над которыми вы работаете. Создавайте отличный код даже в маленьких приложениях.