Spring Dependency Injection в действии

max аватар
124
Находится в разделах:

Предположим, что Ваша компания провела маркетинговые исследования и в результате экспертного анализа пришли к выводу, что вашему заказчику в действительности необходим рыцарь. Да, java класс, который представляет рыцаря. После анализа их требований, вы поняли, что они в действительности хотят от вас реализации класса, который представляет Артура, рыцаря круглого стала, который собирается в походы за поиском Святого Грааля.

Да, это странный запрос, но вы ведь уже привыкли к странным запросам маркетингового тима. Поэтому Вы, без доли сомнения, открываете свой любимый IDE и вбиваете класс.

В этом листинге, имя рыцарю присваивается как параметр конструктора. Его конструктор устанавливает поход рыцаря через создание объекта HolyGrailQuest. Реализация HolyGrailQuest тривиальна:

Глубоко удовлетворенные своей работой, вы с гордостью комитите код в систему версийности кода. Вы хотите показать его маркетинговой команде, но глубоко убеждены, что чего-то не хватает . Вы не написали ни одного юнит-теста.

 

Тестирования рыцаря

Юнит-тестирование - важная часть разработки. 

После написания теста, Вы захотите писать тест для HolyGrailQuest. Но до начала, вы поймете, что KnightOfTheRoundTableTest неявно тестирует HolyGrailQuest.  Также, вы будете сомневаться, сможете ли вы протестировать все непредвиденные ситуации? Что случиться, если метод embark() класса HolyGrailQuest возвратит null? Или что, если он породит GrailNotFoundException?

 

Кто кого вызывает?

Главная проблема с KnightOfTheRoundTable состоит в том, как он получает HolyGrailTest.  Независимо от того создается ли он новый объект HolyGrail или ссылка получается через JNDI, каждый рыцарь должен иметь свой собственный квест.  Поэтому, нет возможности протестировать класс рыцаря в изоляции.  Каждый раз, как вы тестируете KnightOfTheRoundTable, Вы также будете косвенно тестировать HolylGrailQuest.

 

Уменьшение связей через интерфейсы

Вся проблема в связях. KnightOfTheRoundTable статически связан с HolyGrailQuest. Они связаны "наручниками" так, что вы не можете иметь KnightOfTheRoundTable без HolyGrailQuest. 

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

A knight is responsible for getting its own quest, through instantiation or some other means.

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

 

Общепринятой техникой сокращения связей - это сокрытие деталей реализации за интерфейсами так, что реальный класс с  реализацией может быть быть выгружен без ущерба клиентскому классу. Например, предположим, что Вы создали интерфейс Quest.

Затем, вы изменяете HolyGrailQuest для реализации этого интерфейса. Также, заметьте, что embark() теперь возвращает Object и кидает QuestFailedException.

Также, следующий метод должен изменен в KnightOfTheRoundTable чтобы стать совместимым с типом Quest.

Аналогично можно сделать, чтобы KnightOfTheRoundTable реализовывал следующий интерфейс Knight:

Сокрытие реализации класса за интерфейсами - это, безусловно, шаг в правильном направлении. Но есть много разработчиков, которые окажутся в затруднении получить объект Quest. Например, предположим такое возможное изменение KnightOfTheRoundTable:

Здесь класс KnightOfTheRoundTable начинает квест с интерфейса Quest. Но рыцарь и дальше получает специфический тип Quest (HolyGrailQuest).  Это не лучше того, что у нас было. KnightOfTheRoundTable  застрял на одних лишь квестах для Holy Grail и никаких других типах.

 

Брать и отдавать

На этом этапе должен возникнуть следующий вопрос: должен ли рыцарь быть ответственным за получение квеста или кто-то должен "дать" рыцарю квест для старта?

Предположим следующее изменение KnightOfTheRoundTable:

Заметили изменение?  Теперь рыцарю "дают" квест, а не он сам его "добывает". KnightOfTheRoundTable больше не отвечает за получение квестов вручную.  А из-за того, что он знает о квесте лишь через интерфейс Quest,  Вы можете давать ему любую реализацию Quest.  В другой конфигурации, возможно, могла бы быть реализация RescueDamselQuest и т.д. 

По такому же подобию, тест кейс будет следующий:

Dependency injection in action

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

 

Присваивание квеста рыцарю:

Теперь, когда у нас есть класс KnightOfTheRoundTable с возможностью приема любого объекта типа Quest, как мы можем задать, какой Quest должен быть передан?

Действие по созданию ассоциаций между компонентами приложения называется проводкой (wiring).  В Spring существует множество способов "связать" (wire) компоненты между собой, но наиболее распространенным вариантом является использование XML. 

Рассмотрим простой конфигурационный файл Spring, knight.xml, который дает квест (HolyGrailQuest) классу KnightOfTheRoundTable.

Это всего лишь простой метод проводки между бинами.  Теперь, когда мы объявили отношения между рыцарем и квестом, необходимо загрузить XML файл.

 

Смотрим как это все работает

В Spring приложении, BeanFactory загружает определения бина и связывает их вместе. Поскольку бины в примере с рыцарем объявлены в XML файле, XmlBeanFactory - подходящая фабрика для этого примера. Метод main() использует XmlBeanFactory для загрузки knight.xml  и для получения ссылки на объект Knight.

Как только приложение получит ссылку на объект Knight, он просто вызовет embarkOnQuest() метод для начала приключений рыцаря. Заметьте, что класс ничего не знает про квест, который приготовлен для рыцаря. Единственной сущностью, которая знает какой тип квеста будет дан рыцарю - это knight.xml.

 

Мораль басни:

Вся эта история малоприменима в реальной жизни, но на сказочных началах демонстрирует принципы использования Dependency Injection.


Конец
Сэмюэл Смайлс хочет заметить, что из-за отсутствия контроля над собой многие люди всю жизнь проводят в борьбе с трудностями, которые сами же и создали. Их собственная упрямая жестокость не дает им добиться успеха.

барво маэстро!Laughing

*браво маэстро!Laughing

отличное введение!

Так держать! Хотелось бы увидеть подобную статью о модульном тестировании в spring.

max аватар

Материалы по Spring взяты из книги Spring in Action

Замечательная книга, его автора стоит поблагодарить и прочитать ее. 

По возможности, буду выкладывать всевозможные материалы по спрингу - самому интересно. 

Существует ли разница через что грузить бин - через BeanFactory или через AplicationContext?

Отправить комментарий

CAPTCHA
Чтобы оставить комментарий, введите пожалуйста код, изображенный на картинке
Image CAPTCHA
Введите символы, изображенные на картинке