SPRING-SOURCE.RU | |
|
|
Как это обычно делают люди. Менеджер: он зависит от какого-то сервиса или продукта. Сейчас менеджер просто создает этот сервис или продукт самостоятельно, то есть, менеджер делает new service и вызывает его методы. Представим, что у нас есть какая-либо фабрика и мы говорим фабрике, что нужно создавать продукты и она их производит. Но в этом случае мы зависим от фабрики. Так делают многие и это правильно.
Но как можно сделать лучше? Нужно сделать, чтобы менеджер получал, что-то извне, а не создавал. По сути это и называется инъекция зависимости (инверсия контроля). Теперь получается, что не менеджер уже контролирует объекты, а кто-то их контролирует, а менеджеру предоставляет. Мы, как бы, меняем направление движения.
К нам приходит менеджер и говорит: создай мне класс, где Командир будет вести свою армию на Германию. Обычно пользователь сразу начинает создавать класс.
public class Командир { private String имя; private ПоходНаГерманию поход; public Командир(String имя) { this.имя = имя; поход = new ПоходНаГерманию(); } public Богатство пойтиВпоход() throws НеПолучилосьException { return поход.сходить(); } }
Теперь создаем класс Поход
public class ПоходНаГерманию { public ПоходНаГерманию() {} public Богатство пойтиВпоход() throws НеПолучилосьException { Богатство бг = null; побитьВсех(); бг = забратьБогатство(); return бг; } }
После того, как мы все это сделали нам нужно протестировать работу.
public class КомандирTest { @Test public void testПойтиВпоход() throws НеПолучилосьException { Командир командир = new Командир("Санька"); Богатство бг = командир.пойтиВпоход(); assertNotNull(бг); assertTrue(бг.свободноКонвертируемое()); } }
Здесь проверяем, что все прошло успешно. Что здесь нехорошего? В данном случае мы тестируем класс Командир, а неформально еще тестируем и класс ПоходНаГерманию. Мы этого не собирались делать. А что если у нас ошибка в классе ПоходНаГерманию, а не в классе Командир. Что вернет тест? Тест вернет, что у нас ошибка в классе Командир, так как именно в нем мы вызываем метод ПойтиВПоход.
Похожим примером может служить пример – доступ к базе данных. Там создаем метод сохранить или получить. Внутри этих методов мы обращаемся, например, к какой-нибудь базе данных, используя JDBC. Делаем query и получаем результат.
Здесь в поле действия, кроме метода, попадает и JDBC, а этого нам не надо.
Вернемся к походам. Походы желательно тестировать отдельно своим тестом. Если при данной конфигурации мы напишем два теста, то, по сути, будет тестироваться одно и тоже. Как это исправить.
Исправить можно, если послать заглушку классу Поход, которая возвращает true. Как мы пошлем эту заглушку? Никак. Надо менять класс. Либо прибегнуть к IoC.
Еще одно. Что если мы хотим сходить еще в один поход, например , на Америку? Ведь у нас в конструкторе уже определен поход на Германию. А что, если информацию мы будет хранить не только в базе, но еще и в файле? Мы ведь не будем перечислять все возможные способы.
Выходит, что самый напрашивающийся вариант - разделить все на интерфейсы. В данном случае, создать интерфейс Поход в котором будет метод сходить и класс Командир каким-то образом будет работать с этим интерфейсом. Откуда он возьмет реализацию? Самое главное чтобы он не сам ее создавал. Соответственно при тестах в качестве интерфейса поход мы сможем поставить какую-нибудь заглушку. Теперь мы сможем реализовать различные походы и мы сможем получать эти интерфейсы из разных мест.
Создаем интерфейс Поход
public interface iПоход { public Богатство пойтиВпоход() throws НеПолучилосьException; } public class Командир { private String имя; private iПоход поход; public Командир(String имя) { this.имя = имя; поход = new ПоходНаГерманию(); } public Богатство пойтиВпоход() throws НеПолучилосьException { return поход.сходить(); } }
Здесь мы делаем реализацию не конкретного похода, а реализацию интерфейса. Осталось только избавиться от оператора new в конструкторе. Чтобы мы сами ничего не создавали, а только получали.
Избавиться можно двумя путями, либо в конструкторе получить реализацию похода либо через setter метод.
public interface iПоход { public Богатство пойтиВпоход() throws НеПолучилосьException; } public class Командир { private String имя; private iПоход поход; public Командир(String имя) { this.имя = имя; } public void setПоход(iПоход поход) { this.поход = поход; } }
Как теперь происходит тестирование? Создаем класс Командир, в set установили нужный поход и вызвали метод сходить, либо все это можно сделать через Spring.
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd"> <bean id="quest" class="com.example.ПоходНаГерманию"/> <bean id="comandir" class="com.example.Командир"> <constructor-arg value="Жуков"/ > <property name="поход" ref="quest" /> </bean> </beans>
Первым делом надо создать bean id="quest" class="Поход на Германию"
Теперь создает Командир. В командире у нас не было конструктора без параметров и поэтому мы указываем constructor-arg value="Жуков" и затем в property name="поход" мы пишем ref="quest". Теперь в Spring мы можем создать несколько классов Командир, дать им разные имена и послать на разные походы.
public class КомандирApp { public static void main(String[] args) throws Exception { BeanFactory factory = new XmlBeanFactory( new FileSystemResource( "comandir.xml")); Командир командир = (Командир) factory. getBean("comandir"); командир.пойтиВпоход(); } }
Создаем bean factory на основе конфигурационного файла, получаем bean командира и вызываем у него метод Сходить в поход. В какой поход пойдет командир мы определяем в конфигурационном файле.
Приложение основанное на Spring формируется и объектов. То есть мы создаем разные bean и потом передаем их друг другу.
Copyright © 2024 |