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

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

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:

Листинг 3. Передаем зависимость через интерфейс

public class FrogMan {
private Vehicle vehicle;

public FrogMan(Vehicle vehicle) {
this.vehicle = vehicle;
}
// здесь герой борется с преступностью...
}

Такой стиль является сущностью концепции DI - пусть ваши классы принимают зависимости в виде ссылок на интерфейсы, вместо того чтобы создавать их самим или использовать статические ссылки. Листинг 4 показывает, как DI упрощает тест:

Листинг 4. В тесте можно использовать заглушки вместо сложных зависимостей

static class MockVehicle implements Vehicle {
boolean didZoom;

public String zoom() {
this.didZoom = true;
return "Mock Vehicle Zoomed.";
}
}

public void testFrogManFightsCrime() {
MockVehicle mockVehicle = new MockVehicle();

FrogMan hero = new FrogMan(mockVehicle);
hero.fightCrime();

assertTrue(mockVehicle.didZoom);
// другие проверки
}

Этот тест использует вместо FrogMobile самодельный объект-заглушку. Благодаря DI, теперь тесту не нужно создавать сложный объект FrogMobile, ему о нем вообще ничего не надо знать! Все, что нужно знать тесту, - это интерфейс Vehicle. DI, кроме того, что делает тесты проще, также улучшает модульность и сопровождаемость вашего кода. Теперь, если вы захотите поменять FrogMobile на объект другого типа, например FrogBarge, вы можете это сделать не меняя FrogMan. Единственное от чего зависит FrogMan, - это интерфейс.

Однако здесь есть нюанс. Как и я, читая первый раз про Guice, вы можете подумать: "Отлично, теперь все, кто вызывают FrogMan, должны знать о FrogMobile (о его устройстве, зависимостях и т.д.)." Но если бы это было так, концепция DI никогда не стала бы популярной. Чтобы упростить всем жизнь, вы можете создавать фабрики, которые будут управлять созданием объектов и их зависимостей.

Именно при работе с фабриками хороши инфраструктуры. Фабрики требуют много скучного, повторяющегося кода. В лучшем случае, они просто раздражают автора кода (и его читателей), а в худшем случае их вообще не пишут из-за их неудобства. Guice и другие инфраструктуры с DI выполняют роль "суперфабрик", которые вы можете гибко настраивать для конструирования ваших объектов. Конфигурирование инфраструктуры гораздо проще написания собственной фабрики. Как результат, программисту удается написать больше кода в стиле DI. В итоге, мы имеем больше тестов, более качественный код и довольных программистов.

Источник: http://www.ibm.com