SPRING-SOURCE.RU

IoC – инверсия контроля

Как это обычно делают люди. Менеджер: он зависит от какого-то сервиса или продукта. Сейчас менеджер просто создает этот сервис или продукт самостоятельно, то есть, менеджер делает new service и вызывает его методы. Представим, что у нас есть какая-либо фабрика и мы говорим фабрике, что нужно создавать продукты и она их производит. Но в этом случае мы зависим от фабрики. Так делают многие и это правильно.

Но как можно сделать лучше? Нужно сделать, чтобы менеджер получал, что-то извне, а не создавал. По сути это и называется инъекция зависимости (инверсия контроля). Теперь получается, что не менеджер уже контролирует объекты, а кто-то их контролирует, а менеджеру предоставляет. Мы, как бы, меняем направление движения.

Spring архитектура

В чем преимущество и что мы можем от этого получить?

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

                        
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.

IoC. Когда человек зависимости не создает а он их получает.

                        
<?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 и потом передаем их друг другу.

Замечания