Внедрение зависимостей с помощью Guice

Fox populi

Guice — это инфраструктура для внедрения или инъекции зависимостей (dependency injection или сокращенно DI). Я уже несколько лет являюсь активным сторонником использования DI, потому что это улучшает сопровождаемость, тестируемость и гибкость кода. Наблюдая за тем, как разработчики реагируют на Guice, я понял, что лучший способ убедить программиста начать применять новую технологию — сделать ее по-настоящему простой. Guice действительно предельно упрощает DI, и в результате этот подход получил в Google широкое распространение. Я надеюсь, что эта статья поможет сделать и ваше изучение Guice по-настоящему простым.
Для обзора, а не для споров

Guice — далеко не первая DI-инфраструктура. Есть целое множество замечательных инфраструктур (В разделе Ресурсы есть ссылка на страничку сайта PicoContainer, — одной из таких инфраструктур, — где рассказывается об истории развития и взаимоотношениях различных инфраструктур. Позднее появление Guice породило дискуссии о том, какая из существующих инфраструктур лучше и о том, нужна ли вообще еще одна DI-инфраструктура. Как при любом выборе технологии, каждая библиотека имеет свои плюсы и минусы. На мой взгляд, Guice содержит важные нововведения, но в этой статье я не буду ввязываться в споры, а сделаю обзор возможностей Guice. (В Интернете можно найти множество оживленных дискуссий, поискав по словам «guice vs spring».)

Guice 2.0 beta

На момент написания этой статьи, команда Guice работает над версией 2.0 и планирует выпустить ее до конца 2008 года. Бета-версия доступна для скачивания на сайте Google Code (см. ссылку в разделе Ресурсы). Это хорошая новость, потому что команда Guice добавила в эту версию новые возможности, которые позволяют сделать ваш Guice-код более простым для использования и понимания. Несмотря на то, что в бета-версии отсутствуют некоторые возможности, которые будут включены в финальную версию, все-таки это стабильная и высококачественная версия. Фактически Google уже использует эту бета-версию в своих работающих продуктах. Я советую вам сделать также. Я написал эту статью именно для Guice 2.0, рассказав о некоторых новых возможностях, и умолчав о возможностях версии 1.0, не рекомендуемых для использования в 2.0. Разработчики Guice заверили меня, что функциональность, о которой я расскажу, не будет изменяться в финальной версии по сравнению с бета-версией.

Если вы уже понимаете, что такое DI и знаете, почему для работы с ней вам нужна инфраструктура, можете сразу перейти к разделу Простое внедрение зависимостей с помощью Guice. В противном случае читайте дальше, чтобы узнать о преимуществах DI.

Пример использования DI

Я начну с примера. Скажем, я пишу игровое приложение с супергероем и реализую героя с именем Frog Man. Листинг 1 содержит код приложения и мой первый тест. (Я надеюсь, мне не нужно убеждать вас в пользе написания юнит-тестов.)

Листинг 1. Простой герой и его тест
public class FrogMan {
private FrogMobile vehicle = new FrogMobile();
public FrogMan() {}
// здесь герой борется с преступностью…
}

public class FrogManTest extends TestCase {
public void testFrogManFightsCrime() {
FrogMan hero = new FrogMan();
hero.fightCrime();
//делаем несколько проверок…
}
}

Казалось бы, все хорошо, но при запуске теста я получаю исключение, показанное в листинге 2:

Листинг 2. Зависимости могут доставлять неприятности
java.lang.RuntimeException: Refinery startup failure.
at HeavyWaterRefinery.(HeavyWaterRefinery.java:6)
at FrogMobile.(FrogMobile.java:5)
at FrogMan.(FrogMan.java:8)
at FrogManTest.testFrogManFightsCrime(FrogManTest.java:10)

Похоже, что FrogMobile пытается создать объект класса HeavyWaterRefinery, который я не могу создать в моем тесте. Я могу его создать в работающей коммерческой системе, но никто не разрешит мне сделать второй такой же просто для тестирования. В реальной разработке часто существуют подобные зависимости, например от удаленного сервера или базы данных. Настроить такие зависимости и взаимодействовать с ними сложно, и по вине этих зависимостей ошибки в ваших тестах будут встречаться гораздо чаще, чем следовало бы.

Введение в DI

Чтобы избежать этой проблемы, можно создать интерфейс (например, Vehicle) и заставить конструктор вашего класса FrogMan принимать этот интерфейс в качестве аргумента, как показано в листинге 3: