Если нельзя, но очень хочется, то нужно обязательно и ничего в мире не стоит того, чтобы делать из этого проблему!


Интересна Java? Кликай по ссылке и изучай!
Если тебе полезно что-то из того, чем я делюсь в своем блоге - можешь поделиться своими деньгами со мной.
с пожеланием
столько времени читатели провели на блоге - 
сейчас онлайн - 
Показаны сообщения с ярлыком класс. Показать все сообщения
Показаны сообщения с ярлыком класс. Показать все сообщения

понедельник, 19 марта 2012 г.

Java for fun: Что такое Dependency injection, Inversion of Control и почему это возникло. Часть #6

В прошлый раз обещал, что выложу самописный IoC контейнер. Видимо пора бы это сделать уже - столько времени прошло.

Итак исходники качаем тут.

Начнем как всегда с тестов.

package container;
import static org.junit.Assert.*;

import org.junit.Before;
import org.junit.Test;

import container.ioc.*;
import container.flashlight.*;
import container.battery.*;

public class TestBaterry {

    private Container container;
    
    @Before 
    public void initContainer() {
        // обрати внимание перед каждым тестом проинитится контейнер, которому мы сообщили, что 
        // заместь батарейки используй ChinaBattery а вместо фонарика SomeFlashlight
        container = new ContainerImpl(
                 Binder.use(ChinaBattery.class).as(Battery.class), 
                 Binder.use(SomeFlashlight.class).as(Flashlight.class));
    }
    
    @Test
    public void testDischargeNewBattery() {    
        // таким нехитрым способом по интерфейсу мы получаем реализацию
        // контейнер пройдется по всем полям новосозданного объекта и 
        // если там найдет знакомые типы то вставит в них реализации, 
        // в соответствии с настройками, которые мы указали
        Flashlight flashlight = container.get(Flashlight.class);        
        assertFalse(flashlight.isShines());
        
        flashlight.swithOn();        
        assertTrue(flashlight.isShines());
        
        for (int count = 0; count < 1000; count ++) {
            flashlight.swithOff();                            
            flashlight.swithOn();                    
        }
        
        flashlight.swithOn();
        assertFalse(flashlight.isShines());        
    }
    
    @Test
    public void testBadBattery() {                                
        Battery battery = new Battery(){
            @Override
            public boolean getVoltage() {            
                return false;
            }
        };
        
        // а вот так мы вдруг можем передумать и переопределить настройки контейнера
        container.update(Binder.use(battery).as(Battery.class));
        
        Flashlight flashlight = container.get(Flashlight.class);        
        assertFalse(flashlight.isShines());
        
        flashlight.swithOn();        
        assertFalse(flashlight.isShines());            
    }
        
    @Test
    public void testNoGetPowerIfDoubleSwithOn() {                        
        Flashlight flashlight = container.get(Flashlight.class);    
        assertFalse(flashlight.isShines());
        
        for (int count = 0; count < 1000; count ++) {                    
            flashlight.swithOn();                    
        }
        
        assertTrue(flashlight.isShines());            
    }    
    
    @Test 
    public void testNoBatteryNoLight() {        
        container.remove(Battery.class);
        
        Flashlight flashlight = container.get(Flashlight.class);
        
        assertFalse(flashlight.isShines());
        
        flashlight.swithOn();
        
        assertFalse(flashlight.isShines());    
    }

    @Test
    public void integrationTestGetPowerFormNewChinaBattery() {                
        Flashlight flashlight = container.get(Flashlight.class);    
        
        assertFalse(flashlight.isShines());
        
        flashlight.swithOn();
        
        assertTrue(flashlight.isShines());    
    }  
}

Именно так я и написал код, когда я начал разработку этого примера. Никаких классов контейнера естественно не было, и тест не компилился. Но я создал пустые классы с помощью IDE (Ctrl1-1 в Eclipse или Alt-Enter в Idea).

Вот он код пустой
package container.ioc;

public class Binder {    

    public static Binder use(Class<?> classToCreate) {
        return null;
    }
    
    public static Binder use(Object object) {
        return null;
    }

    public Binder as(Class<?> interfaceClass) {
        return null;
    }    
}

package container.ioc;

public interface Container {

    <T> T get(Class<T> interfaceClass);

    void remove(Class<?> clazz);

    void update(Binder binder);
}

package container.ioc;

public class ContainerImpl implements Container {

    public ContainerImpl(Binder...binders) {    
    }
    
    @Override
    public <T> T get(Class<T> interfaceClass) {
        return null;
    }

    @Override
    public void remove(Class<?> clazz) {        
    }

    @Override
    public void update(Binder binder) {
    }
}

Так теперь код хоть компилится! :) После того как была создана интерфейсная часть, я взялся за реализацию с помощью TDD.

Вот те тестовые сценарии, которые я один за другим написал и реализовал. Кода очень много :) потому что должен был проверить всевозможные случаи инъекции с разными полями, с одинаковыми полями, с наследниками, с реализациями, с примитивами и так далее. Если хочешь скипнуть - жми сюда.

Текста на самом деле много. А еще я все классы сделал Inner классами хотя в реальной версии они должны быть отдельными public классами иначе ничего не получится. Работа с иннераклассами у меня у туду.

package container2.ioc.test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;

import org.junit.Test;

import container.ioc.Binder;
import container.ioc.ContainerImpl;

public class IoCTest {
            
    interface Marker1 {
    }
    
    class Marker1Impl implements Marker1{
    }
    
    interface Marker2 {
    }
    
    class Marker2Impl implements Marker2 {
    }
    
    interface Marker3 {
    }
    
    class Marker3Impl implements Marker3 {
    }
    
    interface MainMarker {
    }
    
    private <T> T getImpl(Class<T> mainClass) {
        return (T)new ContainerImpl(
                Binder.use(Marker1Impl.class).as(Marker1.class), 
                Binder.use(Marker2Impl.class).as(Marker2.class),
                Binder.use(Marker3Impl.class).as(Marker3.class),                
                Binder.use(mainClass).as(MainMarker.class)).get(MainMarker.class);
    }
    
    class ClassWithDefaultConstructor implements MainMarker {
        Marker1 marker1; 
        
        public ClassWithDefaultConstructor() {
            
        }
    }
    
    @Test
    public void testWithDefaultConstructor() {
        assertEquals(Marker1Impl.class, 
                getImpl(ClassWithDefaultConstructor.class).marker1.getClass());            
    }
    
    class ClassWithoutAnyConstructors implements MainMarker {
        Marker1 marker1; 
        
        private ClassWithoutAnyConstructors() {        
        }
    }
    
    @Test
    public void testWithoutAnyConstructors() {
        assertEquals(Marker1Impl.class, 
                getImpl(ClassWithoutAnyConstructors.class).marker1.getClass());            
    }
    
    class ClassWithPrivateField implements MainMarker {
        private Marker1 marker1; 
        
        public ClassWithPrivateField() {}

        public Object getMarker1() {
            return marker1;
        }
    }
    
    @Test
    public void testWithPrivateField() {
        assertEquals(Marker1Impl.class, 
                getImpl(ClassWithPrivateField.class).getMarker1().getClass());            
    }
    
    class ClassWithInjectorConstructor implements MainMarker {
        private Marker1 marker1;
        
        public ClassWithInjectorConstructor(Marker1 marker1) {
            this.marker1 = marker1;
        }

        public Marker1 getMarker1() {
            return marker1;
        }            
    }
                
    @Test
    public void testWithInjectorConstructor() {
        assertEquals(Marker1Impl.class, 
                getImpl(ClassWithInjectorConstructor.class).getMarker1().getClass());            
    }
    
    class СlassWithInjectorConstructorAndTwoFields implements MainMarker {
        
        private Marker1 marker1;
        private Marker2 marker2;
        
        public СlassWithInjectorConstructorAndTwoFields(Marker2 marker2, Marker1 marker1) {
            this.marker1 = marker1;
            this.marker2 = marker2;
        }

        public Marker1 getMarker1() {
            return marker1;
        }
        
        public Marker2 getMarker2() {
            return marker2;
        }    
    
    }
    
    @Test
    public void testWithInjectorConstructorAndTwoFieldsCheckField1() {
        assertEquals(Marker1Impl.class, 
                getImpl(СlassWithInjectorConstructorAndTwoFields.class).getMarker1().getClass());            
    }
    
    @Test
    public void testWithInjectorConstructorAndTwoFieldsCheckField2() {
        assertEquals(Marker2Impl.class, 
                getImpl(СlassWithInjectorConstructorAndTwoFields.class).getMarker2().getClass());            
    }    
    
    class СlassWithPrivateInjectorConstructorAndTwoFields implements MainMarker {
        private Marker1 marker1;
        private Marker2 marker2;
        
        private СlassWithPrivateInjectorConstructorAndTwoFields(Marker2 marker2, Marker1 marker1) {
            this.marker1 = marker1;
            this.marker2 = marker2;
        }

        public Marker1 getMarker1() {
            return marker1;
        }
        
        public Marker2 getMarker2() {
            return marker2;
        }        
    }
    
    @Test
    public void testWithPrivateInjectorConstructorAndTwoFieldsCheckField1() {
        assertEquals(Marker1Impl.class, 
                getImpl(СlassWithPrivateInjectorConstructorAndTwoFields.class).getMarker1().getClass());            
    }
    
    @Test
    public void testWithPrivateInjectorConstructorAndTwoFieldsCheckField2() {
        assertEquals(Marker2Impl.class, 
                getImpl(СlassWithPrivateInjectorConstructorAndTwoFields.class).getMarker2().getClass());            
    }
    
    class ClassWithTwoConstructors implements MainMarker {
        private Marker1 marker1; 
        
        public ClassWithTwoConstructors() {
            
        }
        
        private ClassWithTwoConstructors(Marker1 marker) {
            this.marker1 = marker;
        }
        
        public Object getMarker1() {
            return marker1;
        }
    }
    
    @Test
    public void testWithTwoConstructors() {
        assertEquals(Marker1Impl.class, 
                getImpl(ClassWithTwoConstructors.class).getMarker1().getClass());            
    }
    
    class ClassWithTwoConstructorsWitnSameParametersCount implements MainMarker {
        private Marker1 marker1; 
            
        private ClassWithTwoConstructorsWitnSameParametersCount(Marker1 marker) {
            this.marker1 = marker;
        }
        
        private ClassWithTwoConstructorsWitnSameParametersCount(String string) {
        }
        
        public Object getMarker1() {
            return marker1;
        }
    }
    
    @Test
    public void testWithTwoConstructorsWithSameParametersCount() {
        assertEquals(Marker1Impl.class, 
                getImpl(ClassWithTwoConstructorsWitnSameParametersCount.class).getMarker1().getClass());            
    }
    
    class ClassWithTwoFieldsAndOnlyOneInjectedViaConstructor implements MainMarker {
        private Marker1 marker1; 
        private Marker2 marker2;
        private boolean costructorCall = false;
            
        private ClassWithTwoFieldsAndOnlyOneInjectedViaConstructor(Marker1 marker) {
            this.costructorCall = true;
            this.marker1 = marker;
        }
            
        public Object getMarker1() {
            return marker1;
        }
        
        public Object getMarker2() {
            return marker2;
        }

        public boolean isCostructorCall() {
            return costructorCall;
        }
    }
    
    @Test
    public void testWithTwoFieldsAndOnlyOneInjectedViaConstructor1() {
        assertEquals(Marker1Impl.class, 
                getImpl(ClassWithTwoFieldsAndOnlyOneInjectedViaConstructor.class).getMarker1().getClass());            
    }
    
    @Test
    public void testWithTwoFieldsAndOnlyOneInjectedViaConstructor2() {
        assertEquals(Marker2Impl.class, 
                getImpl(ClassWithTwoFieldsAndOnlyOneInjectedViaConstructor.class).getMarker2().getClass());            
    }
    
    @Test
    public void testWithTwoFieldsAndOnlyOneInjectedViaConstructor_constructorUsed() {
        assertTrue(getImpl(ClassWithTwoFieldsAndOnlyOneInjectedViaConstructor.class).isCostructorCall());            
    }
    
    class ClassWithTwoFieldsAndOtherOneInjectedViaConstructor implements MainMarker {
        private Marker1 marker1; 
        private Marker2 marker2;
        private boolean costructorCall = false;
            
        private ClassWithTwoFieldsAndOtherOneInjectedViaConstructor(Marker2 marker) {
            this.costructorCall = true;
            this.marker2 = marker;
        }
            
        public Object getMarker1() {
            return marker1;
        }
        
        public Object getMarker2() {
            return marker2;
        }

        public boolean isCostructorCall() {
            return costructorCall;
        }
    }
    
    @Test
    public void testWithTwoFieldsAndOtherOneInjectedViaConstructor_CheckField1() {
        assertEquals(Marker1Impl.class, 
                getImpl(ClassWithTwoFieldsAndOtherOneInjectedViaConstructor.class).getMarker1().getClass());            
    }
    
    @Test
    public void testWithTwoFieldsAndOtherOneInjectedViaConstructor_CheckField2() {
        assertEquals(Marker2Impl.class, 
                getImpl(ClassWithTwoFieldsAndOtherOneInjectedViaConstructor.class).getMarker2().getClass());            
    }
    
    @Test
    public void testWithTwoFieldsAndOtherOneInjectedViaConstructor_constructorUsed() {
        assertTrue(getImpl(ClassWithTwoFieldsAndOtherOneInjectedViaConstructor.class).isCostructorCall());            
    }
    
    class ClassWithThreeFieldsAndOnlyOneInjectedViaConstructor implements MainMarker {
        private Marker1 marker1; 
        private Marker2 marker2;
        private boolean costructorCall = false;
        private Marker3 marker3;
            
        private ClassWithThreeFieldsAndOnlyOneInjectedViaConstructor(Marker3 marker) {
            this.costructorCall = true;
            this.marker3 = marker;
        }
            
        public Object getMarker1() {
            return marker1;
        }
        
        public Object getMarker2() {
            return marker2;
        }

        public boolean isCostructorCall() {
            return costructorCall;
        }

        public Object getMarker3() {
            return marker3;
        }
    }
    
    @Test
    public void testWithThreeFieldsAndOtherOneInjectedViaConstructor_CheckField1() {
        assertEquals(Marker1Impl.class, 
                getImpl(ClassWithThreeFieldsAndOnlyOneInjectedViaConstructor.class).getMarker1().getClass());            
    }
    
    @Test
    public void testWithThreeFieldsAndOtherOneInjectedViaConstructor_CheckField2() {
        assertEquals(Marker2Impl.class, 
                getImpl(ClassWithThreeFieldsAndOnlyOneInjectedViaConstructor.class).getMarker2().getClass());            
    }
    
    
    @Test
    public void testWithThreeFieldsAndOtherOneInjectedViaConstructor_CheckField3() {
        assertEquals(Marker3Impl.class, 
                getImpl(ClassWithThreeFieldsAndOnlyOneInjectedViaConstructor.class).getMarker3().getClass());            
    }
    
    @Test
    public void testWithThreeFieldsAndOtherOneInjectedViaConstructor_constructorUsed() {
        assertTrue(getImpl(ClassWithThreeFieldsAndOnlyOneInjectedViaConstructor.class).isCostructorCall());            
    }
    
    class Marker1Fasade implements Marker1 {

        private Marker1 marker;

        public Marker1Fasade(Marker1 marker) {
            this.marker = marker; 
        }
        
        public Object getMarker() {
            return marker;
        }
        
    }
    
    class ClassWithTwoFieldsAndOnlyOneInjectedViaConstructorWithFasade implements MainMarker {
        private Marker1 marker1; 
        private Marker2 marker2;
            
        private ClassWithTwoFieldsAndOnlyOneInjectedViaConstructorWithFasade(Marker1 marker) {
            this.marker1 = new Marker1Fasade(marker);
        }
            
        public Object getMarker1() {
            return marker1;
        }
        
        public Object getMarker2() {
            return marker2;
        }
    }
    
    @Test
    public void testWithTwoFieldsAndOnlyOneInjectedViaConstructorWithFasade_checkThatNoReflectionInjectionIfConstructorInjected() {
        ClassWithTwoFieldsAndOnlyOneInjectedViaConstructorWithFasade impl = 
            getImpl(ClassWithTwoFieldsAndOnlyOneInjectedViaConstructorWithFasade.class);
        
        assertEquals(Marker1Fasade.class, impl.getMarker1().getClass());            
        assertEquals(Marker1Impl.class, ((Marker1Fasade)impl.getMarker1()).getMarker().getClass());
    }
    
    class ClassWithThreeFieldsAndTwoInjectedViaConstructor implements MainMarker {
        private Marker3 marker3;
        private boolean costructorCall = false;
        private Marker1 marker1; 
        private Marker2 marker2;
            
        private ClassWithThreeFieldsAndTwoInjectedViaConstructor(Marker3 marker3, Marker2 marker2) {
            this.costructorCall = true;
            this.marker3 = marker3;
            this.marker2 = marker2;
        }
            
        public Object getMarker1() {
            return marker1;
        }
        
        public Object getMarker2() {
            return marker2;
        }

        public boolean isCostructorCall() {
            return costructorCall;
        }

        public Object getMarker3() {
            return marker3;
        }
    }
    
    @Test
    public void testWithThreeFieldsAndTwoInjectedViaConstructor_CheckField1() {
        assertEquals(Marker1Impl.class, 
                getImpl(ClassWithThreeFieldsAndTwoInjectedViaConstructor.class).getMarker1().getClass());            
    }
    
    @Test
    public void testWithThreeFieldsAndTwoInjectedViaConstructor_CheckField2() {
        assertEquals(Marker2Impl.class, 
                getImpl(ClassWithThreeFieldsAndTwoInjectedViaConstructor.class).getMarker2().getClass());            
    }
    
    @Test
    public void testWithThreeFieldsAndTwoInjectedViaConstructor_CheckField3() {
        assertEquals(Marker3Impl.class, 
                getImpl(ClassWithThreeFieldsAndTwoInjectedViaConstructor.class).getMarker3().getClass());            
    }
    
    @Test
    public void testWithThreeFieldsAndTwoInjectedViaConstructor_constructorUsed() {
        assertTrue(getImpl(ClassWithThreeFieldsAndTwoInjectedViaConstructor.class).isCostructorCall());            
    }    
    
    class Marker3Fasade implements Marker3 {

        private Marker3 marker;

        public Marker3Fasade(Marker3 marker) {
            this.marker = marker; 
        }
        
        public Object getMarker() {
            return marker;
        }    
    }
    
    class ClassWithThreeFieldsAndTwoConstructors implements MainMarker {
        private Marker3 marker3;
        private boolean isCommonCostructorCall = false;
        private Marker1 marker1; 
        private Marker2 marker2;
        
        private ClassWithThreeFieldsAndTwoConstructors(Marker1 marker1) {
            this.marker1 = marker1;
        }
        
        private ClassWithThreeFieldsAndTwoConstructors(Marker3 marker3, Marker2 marker2) {
            this.isCommonCostructorCall = true;
            this.marker3 = new Marker3Fasade(marker3);
            this.marker2 = marker2;
        }
            
        public Object getMarker1() {
            return marker1;
        }
        
        public Object getMarker2() {
            return marker2;
        }

        public boolean isCommonCostructorCall() {
            return isCommonCostructorCall;
        }
        
        public Object getMarker3() {
            return marker3;
        }
    }
    
    @Test
    public void testWithThreeFieldsAndTwoConstructors_CheckField1() {
        assertEquals(Marker1Impl.class, 
                getImpl(ClassWithThreeFieldsAndTwoConstructors.class).getMarker1().getClass());            
    }
    
    @Test
    public void testWithThreeFieldsAndTwoConstructors_CheckField2() {
        assertEquals(Marker2Impl.class, 
                getImpl(ClassWithThreeFieldsAndTwoConstructors.class).getMarker2().getClass());            
    }
    
    @Test
    public void testWithThreeFieldsAndTwoConstructors_CheckField3() {        
        ClassWithThreeFieldsAndTwoConstructors impl = 
            getImpl(ClassWithThreeFieldsAndTwoConstructors.class);
        
        assertEquals(Marker3Fasade.class, impl.getMarker3().getClass());            
        assertEquals(Marker3Impl.class, ((Marker3Fasade)impl.getMarker3()).getMarker().getClass());        
    }
    
    @Test
    public void testWithThreeFieldsAndTwoConstructors_constructorUsed() {
        assertTrue(getImpl(ClassWithThreeFieldsAndTwoConstructors.class).isCommonCostructorCall());            
    }    
    
    class ClassWithThreeFieldsAndThreeConstructorsOneIsNotUsed implements MainMarker {
        private Marker3 marker3;
        private boolean isCommonCostructorCall = false;
        private Marker1 marker1; 
        private Marker2 marker2;
        
        private ClassWithThreeFieldsAndThreeConstructorsOneIsNotUsed(Marker1 marker1) {
            this.marker1 = marker1;
        }
        
        private ClassWithThreeFieldsAndThreeConstructorsOneIsNotUsed(Marker3 marker3, Marker2 marker2) {
            this.isCommonCostructorCall = true;
            this.marker3 = new Marker3Fasade(marker3);
            this.marker2 = marker2;
        }
        
        private ClassWithThreeFieldsAndThreeConstructorsOneIsNotUsed(Marker3 marker3, Marker2 marker2, String bla) {
            this.marker3 = marker3;
            this.marker2 = marker2;
        }
            
        public Object getMarker1() {
            return marker1;
        }
        
        public Object getMarker2() {
            return marker2;
        }

        public boolean isCommonCostructorCall() {
            return isCommonCostructorCall;
        }
        
        public Object getMarker3() {
            return marker3;
        }
    }
    
    @Test
    public void testWithThreeFieldsAndThreeConstructorsOneIsNotUsed_CheckField1() {
        assertEquals(Marker1Impl.class, 
                getImpl(ClassWithThreeFieldsAndThreeConstructorsOneIsNotUsed.class).getMarker1().getClass());            
    }
    
    @Test
    public void testWithThreeFieldsAndThreeConstructorsOneIsNotUsed_CheckField2() {
        assertEquals(Marker2Impl.class, 
                getImpl(ClassWithThreeFieldsAndThreeConstructorsOneIsNotUsed.class).getMarker2().getClass());            
    }
    
    @Test
    public void testWithThreeFieldsAndThreeConstructorsOneIsNotUsed_CheckField3() {        
        ClassWithThreeFieldsAndThreeConstructorsOneIsNotUsed impl = 
            getImpl(ClassWithThreeFieldsAndThreeConstructorsOneIsNotUsed.class);
        
        assertEquals(Marker3Fasade.class, impl.getMarker3().getClass());            
        assertEquals(Marker3Impl.class, ((Marker3Fasade)impl.getMarker3()).getMarker().getClass());        
    }
    
    @Test
    public void testWithThreeFieldsAndThreeConstructorsOneIsNotUsed_constructorUsed() {
        assertTrue(getImpl(ClassWithThreeFieldsAndThreeConstructorsOneIsNotUsed.class).isCommonCostructorCall());            
    }
}

Постепенно появлялась реализация. После несокльких подходов к коду я родил вот эти реализации

package container.ioc;

public class Binder {

    Class<?> interfaceClass;
    Class<?> classToCreate;
    Object object;

    public Binder(Class<?> classToCreate) {
        this.classToCreate = classToCreate;
    }

    public Binder(Object object) {
        this.object = object;
    }

    public static Binder use(Class<?> classToCreate) {
        return new Binder(classToCreate);
    }
    
    public static Binder use(Object object) {
        return new Binder(object);
    }

    public Binder as(Class<?> interfaceClass) {
        this.interfaceClass = interfaceClass;
        return this;
    }
    
}

package container.ioc;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.LinkedList;
import java.util.List;

public class ContainerImpl implements Container {

    private Collection<Binder> binders;

    public ContainerImpl(Binder...binders) {
        this.binders = new LinkedList<Binder>(Arrays.asList(binders));
    }
    
    @Override
    public <T> T get(Class<T> interfaceClass) {
        Binder binder = find(interfaceClass);
        
        if (binder.object != null) {
            return (T) binder.object;            
        }
        
        Class<?> classToCreate = binder.classToCreate;
        
        List<Constructor<?>> constructors = getCostructors(classToCreate);
        
        List<Object> dependencies = getObjectsFor(onlyInterfaceTypes(getTypes(getFields(classToCreate))));                                                    
                
        return (T) foundCommonConstructor(constructors, dependencies).newInstanceFor(dependencies);                                     
    }

    private InstanceMaker foundCommonConstructor(
            List<Constructor<?>> constructors, List<Object> dependencies) 
    {
        sortByParameterCount(constructors);
        
        for (Constructor<?> constructor : constructors) {
            if (sufficiently(constructor.getParameterTypes(), dependencies)) {
                return new InstanceMaker(constructor); 
            }
        }        
        
        throw new RuntimeException("Constructor not found");
    }

    private void sortByParameterCount(List<Constructor<?>> constructors) {
        Collections.sort(constructors, new Comparator<Constructor<?>>() {
            @Override
            public int compare(Constructor<?> constructor1, Constructor<?> constructor2) {
                return constructor2.getParameterTypes().length - 
                    constructor1.getParameterTypes().length;
            }                
        });
    }    

    private boolean sufficiently(Class<?>[] parameterTypes, List<Object> dependencies) {        
        for (Class<?> clazz : parameterTypes) {
            if (notIn(clazz, dependencies)) {
                return false;
            }
        }
        return true;        
    }

    private boolean notIn(Class<?> clazz, List<Object> dependencies) {
        for (Object dependency : dependencies) {
            if (clazz.isAssignableFrom(dependency.getClass())) {
                return false;
            }
        }
        return true;
    }

    private List<Class<?>> getTypes(Collection<Field> fields) {
        List<Class<?>> result = new LinkedList<Class<?>>();
        for (Field field : fields) {            
            result.add(field.getType());
        }
        return result;
    }
    
    private List<Class<?>> onlyInterfaceTypes(List<Class<?>> classes) {
        List<Class<?>> result = new LinkedList<Class<?>>();
        for (Class<?> clazz : classes) {
            
            try {
                find(clazz);
                result.add(clazz);
            } catch (RuntimeException e) {
                continue;
            }
        }
        return result;                
    }
    
    private Binder find(Class<?> interfaceClass) {
        for (Binder binder : binders) {
            if (binder.interfaceClass.equals(interfaceClass)) {
                return binder;
            }
        }
        throw new RuntimeException("Dependency class not found for interface" + interfaceClass.getName());
    }

    private List<Object> getObjectsFor(List<Class<?>> parameterTypes) {
        List<Object> result = new LinkedList<Object>();
        for (Class<?> clazz : parameterTypes) {
            result.add(get(clazz));
        }
        return result;        
    }
    
    private List<Constructor<?>> getCostructors(Class<?> classToCreate) {
        return Arrays.asList(classToCreate.getDeclaredConstructors());        
    }
    
    private Collection<Field> getFields(Class<?> classToCreate) {
        return Arrays.asList(classToCreate.getDeclaredFields());
    }

    @Override
    public void remove(Class<?> clazz) {
        for (Binder binder : binders) {
            if (binder.interfaceClass.equals(clazz)) {
                binders.remove(binder);
                return;
            }
        }        
    }

    @Override
    public void update(Binder binder) {
        remove(binder.interfaceClass);
        binders.add(binder);
    }
}

package container.ioc;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.LinkedList;
import java.util.List;

public class InstanceMaker {
    private Constructor<?> constructor;
    
    public InstanceMaker(Constructor<?> constructor) {
        this.constructor = constructor;
    }
    
    public Object newInstanceFor(List<Object> dependencies) {
        List<Object> foundDependencies = getDependenciesFor(constructor, dependencies);
        Object object = getObject(constructor, foundDependencies);            
        dependencies.removeAll(foundDependencies);
            
        return injectToField(object, dependencies);
    }
    
    private List<Object> getDependenciesFor(Constructor<?> constructor, List<Object> dependencies) {
        List<Object> result = new LinkedList<Object> ();
        for (Class<?> parameterType : constructor.getParameterTypes()) {
            for (Object dependency : dependencies) {
                if (parameterType.isAssignableFrom(dependency.getClass())) { 
                    result.add(dependency);
                    break; 
                }
            }
        }
        return result;
    }
        
    private Object getObject(Constructor<?> constructor, List<Object> dependencies) {
        try {
            constructor.setAccessible(true);
            Object newInstance = constructor.newInstance(dependencies.toArray(new Object[0]));
            constructor.setAccessible(false);
            
            return newInstance;
        } catch (IllegalArgumentException e) {
            throw new RuntimeException(e);
        } catch (InstantiationException e) {
            throw new RuntimeException(e);
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        } catch (InvocationTargetException e) {
            throw new RuntimeException(e);
        }
    }
    
    private <T> T injectToField(T object, List<Object> dependencies) {
        for (Object dependency : dependencies) {
            Injector.injectAll(object, dependency);
        }
        return object;
    }
}

Классы достаточно сложновастые, но рефлексия все же. Библиотеку Fest Reflection я не хотел подключать из за лишней jar-dependency. В следующий раз попробую сделаю с ней - за одно попиарю этот классный инструмент для работы с рефлексией.

В будущих планах есть сделать то же но со Spring IoC. Ждите, продолжение следует..

понедельник, 10 октября 2011 г.

Java for fun: Что такое Dependency injection, Inversion of Control и почему это возникло. Часть #5

В прошлом посту мы использовали простую фабрику для создания фонарика с уже вставленными батарейками.

Рассмотрим теперь вариант legаcy code, когда у нас в конструкторе фонарика захардкоджен тип батарейки. Очень неприятный момент потому как классы сцеплены между собой жестко. Усугубим его тем фактом, что менять фонарик нельзя (маленькие рефакторинги допустимы, но как и раньше конструктор фонарика должен в себе содержать вызов конструктора батарейки). Заданием нашим будет проверить этот самый фонарик в отрыве от батарейки которую он упорно инстанциирует.

Вот фонарик.

public class SomeFlashlight implements Flashlight {

    private Battery battery;
    private boolean swithOn;
    
    public SomeFlashlight() {
        this.swithOn = false;
        this.battery = new ChinaBattery(); // обратите внимание на эту наглость!
    }
    
    @Override
    public boolean isShines() {
        return (battery != null) && swithOn;
    }

    @Override
    public void swithOn() {
        if (!swithOn && battery != null) {
            swithOn = battery.getVoltage();             
        }            
    }

    @Override
    public void swithOff() {
        swithOn = false;
    }

}

Как его протестировать со другой батарейкой не добавляя никаких конструкторов, не нарушая инкапсуляцию объекта, изменением модификатора доступа поля battery?

Надо локализировать вызов конструктора ChinaBattery в отдельном методе, который позже переопределить в наследнике. Выглядит это так.

public class SomeFlashlight implements Flashlight {

    private Battery battery;
    private boolean swithOn;
    
    public SomeFlashlight() {
        this.swithOn = false;
        this.battery = getBattery(); // вот отсюда ...
    }

    // ... вот сюда инкапсулировали мы зависимость.  
    // protected для того, чтобы можно было субклассировать и переопределить этот метод
    protected Battery getBattery() {
        return new ChinaBattery();
    }
    
...

А переопределять будем уже в тесте вот так

public class TestBaterry {

...

    // внимание! это поле теста
    private Battery battery;
 
    // субклассируем SomeFlashlight и переопределяем в нем getBattery
    class MockedSomeFlashlight extends SomeFlashlight {
     
        @Override
        protected Battery getBattery() {
            return battery;
        }
    }
 
    @Test
    public void testDischargeNewBattery() {
        // устанавливаем себе батарейку такую как захотим
        this.battery = new DisposableBattery();

        // и инстанциируем наш субкласс
        MockedSomeFlashlight flashlight = new MockedSomeFlashlight();
        assertFalse(flashlight.isShines());

        flashlight.swithOn();
        assertTrue(flashlight.isShines());

        flashlight.swithOff();
        assertFalse(flashlight.isShines());

        flashlight.swithOn();
        assertFalse(flashlight.isShines());
    }

...

Вот так я иногда поступаю, когда в коде нельзя ничего делать ручками, а значит зависимость разорвать не получается, а значит DI через конструктор или сеттер не поможет.

Исходники этого примера можно качнуть тут.

суббота, 23 июля 2011 г.

Java for fun: Что такое Dependency injection, Inversion of Control и почему это возникло. Часть #4

На этот раз воспользуемся простой фабрикой для создания фонарика с уже вставленными батарейками. Напомню в прошлый раз мы научились использовать setter для инъекции.

Скажу сразу исходники этого примера можно качнуть тут.

Чтобы не дать возможность никому создавать фонарик мы сделаем его конструктор package-защищенным, так же как и сам класс.

package factory.flashlight;

import factory.battery.Battery;

// Класс защищен потому что мы не хотим, чтобы кто-то из другого пакета
// вызывал его. Только фабрика, которая находится в том же пакете. 
class SomeFlashlight implements Flashlight {

    private Battery battery;
    private boolean swithOn;
    
    // Констурктор так же защищен. Береженого Бог бережет.  
    SomeFlashlight(Battery battery) {
        this.swithOn = false;
        this.battery = battery;
    }
    
    @Override
    public boolean isShines() {
        return (battery != null) && swithOn;
    }

    @Override
    public void swithOn() {
        if (!swithOn && battery != null) {
            swithOn = battery.getVoltage();             
        }            
    }

    @Override
    public void swithOff() {
        swithOn = false;
    }
}

Интерфейс фонарика без изменений.

package factory.flashlight;

public interface Flashlight {

    void swithOn();
    
    void swithOff();

    boolean isShines();

}

Появился новый класс - фабрика, которая устанавливает в новый фонарик батпрейку.

package factory.flashlight;

import factory.battery.BatteryFactory;

public class FlashLightFactory {
    
    // простая фабрика знает, какой фонарик и какую батарейку использовать
    // но возвращает интерфейс!
    public Flashlight getFlashlight() {
        return new SomeFlashlight(new BatteryFactory().getBattery());        
    }
    
}

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

Интерфейс батарейки без изменений.

package factory.battery;

public interface Battery {

    boolean getVoltage();

}

А вот с созданием батарейки я учудил так же как и с фонариком. Есть фабрика батареек и только она знает, какие батарейки выпускает.

package factory.battery;

public class BatteryFactory {
    
    // Заметь, везде где только можно возвращается интерфейс!
    public Battery getBattery() {
        return new ChinaBattery();        
    }
    
}

А вот, собственно, и батарейка. Никто не узнает что в фонариках используется китайская батарейка - только батареечная фабрика. О фабрике батареек знает фабрика фонариков, но только о том, что она есть и может выпускать батарейки.

package factory.battery;

// класс скрыт в пакете
class ChinaBattery implements Battery {

    private int power = 5;     
    
    @Override
    public boolean getVoltage() {
        if (power > 0) {
            power--;
            return true;
        }
        
        return false;
    }

}

Посмотрим тесты:

package factory;
import static org.junit.Assert.*;

import org.junit.Test;

import factory.flashlight.FlashLightFactory;
import factory.flashlight.Flashlight;

public class TestBaterry {
            
    @Test
    public void testDischargeNewBattery() {                                
        Flashlight flashlight = new FlashLightFactory().getFlashlight();        
        assertFalse(flashlight.isShines());
        
        flashlight.swithOn();        
        assertTrue(flashlight.isShines());
        
        for (int count = 0; count < 1000; count ++) {
            flashlight.swithOff();                            
            flashlight.swithOn();                    
        }
        
        flashlight.swithOn();
        assertFalse(flashlight.isShines());        
    }
        
    @Test
    public void testNoGetPowerIfDoubleSwithOn() {                        
        Flashlight flashlight = new FlashLightFactory().getFlashlight();    
        assertFalse(flashlight.isShines());
        
        for (int count = 0; count < 1000; count ++) {                    
            flashlight.swithOn();                    
        }
        
        assertTrue(flashlight.isShines());            
    }    

    @Test
    public void integrationTestGetPowerFormNewChinaBattery() {                
        Flashlight flashlight = new FlashLightFactory().getFlashlight();    
        
        assertFalse(flashlight.isShines());
        
        flashlight.swithOn();
        
        assertTrue(flashlight.isShines());    
    }
    
}
Т.к. теперь нет возможности получить фонарик без батарейки и извлечь батарейку, то и тестов поменьше будет. Мы пользуемся фабрикой чтобы получить фонарик, от вызова к вызову фонарик будет с заряженной батарейкой на борту. Вот и все пока. Дальше мы попробуем разорвать зависимость, если нам для этого не подходит ни DI через конструктор ни через setter. Читаем тут....

среда, 13 июля 2011 г.

Java for fun: Что такое Dependency injection, Inversion of Control и почему это возникло. Часть #3

Привет!! Вчера я писал про инъекцию с помощью конструктора и, как результат, композицию из зависимого объекта и объекта, его использующего. Но фонарик мы привыкли видеть со съемными батарейками, а потому представляю слегка модифицированную версию вчерашнего кода. Итак инъекция посредством get'тера.

Скажу сразу исходники этого примера можно качнуть тут.

Начнем как всегда с интерфейсов. Описание батарейки - не менялся:

package getter.battery;

public interface Battery {

    boolean getVoltage();

}

Описание фонарика - добавили метод установки батарейки (setBattery):

package getter.flashlight;

import getter.battery.Battery;

public interface Flashlight {

    void swithOn();
    
    void swithOff();

    boolean isShines();

    void setBattery(Battery battery);

}

Дальше у нас пойдет китайская батарейка - она так же не менялась:

package getter.battery;

public class ChinaBattery implements Battery {

    private int power = 5; 
    
    @Override
    public boolean getVoltage() {
        if (power > 0) {
            power--;
            return true;
        }
        
        return false;
    }

}

Немного поменялся класс фонарика:

- Конструктор, принимающий батарейку, был удален. Напомню он выглядел так:

public SomeFlashlight(Battery battery) {
    this.battery = battery;
    this.swithOn = false;
}

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

public SomeFlashlight() {
    this.swithOn = false;
}

- Так же был добавлен один setter для установки батарейки на место. Метод определен в интерфейсе.

public void setBattery(Battery battery) {
    this.battery = battery;
}

Вот класс целиком и полностью:

package getter.flashlight;

import getter.battery.Battery;

public class SomeFlashlight implements Flashlight {

    private Battery battery;
    private boolean swithOn;
    
    public SomeFlashlight() {
        this.swithOn = false;
    }
    
    public void setBattery(Battery battery) {
        this.battery = battery;
    }

    @Override
    public boolean isShines() {
        return (battery != null) && swithOn;
    }

    @Override
    public void swithOn() {
        if (!swithOn && battery != null) {
            swithOn = battery.getVoltage();             
        }            
    }

    @Override
    public void swithOff() {
        swithOn = false;
    }

}

А вот и тесты. Они так же поменялись - как минимум был добавлен тест на проверку "а что, если во время работы батарейки нагло извлекли из фонарика?"

package getter;
import static org.junit.Assert.*;
import getter.battery.Battery;
import getter.battery.ChinaBattery;
import getter.flashlight.Flashlight;
import getter.flashlight.SomeFlashlight;

import org.junit.Test;

public class TestBaterry {
        
    class DisposableBattery implements Battery{

        private boolean full = true;
        
        @Override
        public boolean getVoltage() {            
            if (full) {
                full = false;
                return true;
            }
            return false;
        }
    }
    
    @Test
    public void testDischargeNewBattery() {                        
        Battery battery = new DisposableBattery();
        
        Flashlight flashlight = new SomeFlashlight();
        flashlight.setBattery(battery);
        
        assertFalse(flashlight.isShines());
        
        flashlight.swithOn();        
        assertTrue(flashlight.isShines());
        
        flashlight.swithOff();        
        assertFalse(flashlight.isShines());
        
        flashlight.swithOn();        
        assertFalse(flashlight.isShines());        
    }
    
    @Test
    public void testChangeDischargedBattery() {                        
        Battery battery = new DisposableBattery();
        
        Flashlight flashlight = new SomeFlashlight();
        flashlight.setBattery(battery);
        
        assertFalse(flashlight.isShines());
        
        flashlight.swithOn();        
        assertTrue(flashlight.isShines());
        
        flashlight.swithOff();        
        assertFalse(flashlight.isShines());
        
        flashlight.setBattery(new DisposableBattery());
        
        flashlight.swithOn();        
        assertTrue(flashlight.isShines());        
    }
    
    @Test
    public void testBadBattery() {                        
        Battery battery = new Battery(){
            @Override
            public boolean getVoltage() {            
                return false;
            }
        };
        
        Flashlight flashlight = new SomeFlashlight();        
        flashlight.setBattery(battery);
        assertFalse(flashlight.isShines());
        
        flashlight.swithOn();        
        assertFalse(flashlight.isShines());            
    }
    
    @Test
    public void testNoGetPowerIfDoubleSwithOn() {                        
        Battery battery = new DisposableBattery();
        
        Flashlight flashlight = new SomeFlashlight();
        flashlight.setBattery(battery);
        assertFalse(flashlight.isShines());
        
        flashlight.swithOn();        
        assertTrue(flashlight.isShines());
        
        flashlight.swithOn();        
        assertTrue(flashlight.isShines());            
    }
            
    @Test 
    public void testNoBatteryNoLight() {        
        Flashlight flashlight = new SomeFlashlight();
        
        assertFalse(flashlight.isShines());
        
        flashlight.swithOn();
        
        assertFalse(flashlight.isShines());    
    }
    
    @Test 
    public void testRemoveBatteryFromFlashlightDurringLightOn() {        
        Battery battery = new DisposableBattery();
        
        Flashlight flashlight = new SomeFlashlight();
        flashlight.setBattery(battery);
                
        flashlight.swithOn();        
        assertTrue(flashlight.isShines());
        
        flashlight.setBattery(null);
        assertFalse(flashlight.isShines());
    }
    
    @Test
    public void integrationTestGetPowerFormNewChinaBattery() {                
        Battery battery = new ChinaBattery();
        
        Flashlight flashlight = new SomeFlashlight();
        flashlight.setBattery(battery);
        
        assertFalse(flashlight.isShines());
        
        flashlight.swithOn();
        
        assertTrue(flashlight.isShines());    
    }
    
}

Многа букоф :)

Глядя на тесты (как документацию), можно заметить что изначально фонарик не рабочий

Flashlight flashlight = new SomeFlashlight();      
assertFalse(flashlight.isShines());

А еще если батарейки высунуть, то он перестанет светиться

assertTrue(flashlight.isShines());        
flashlight.setBattery(null);
assertFalse(flashlight.isShines());

Во всех остальных случаях, сразу после создания фонарика в него вставляются батарейки

Battery battery = ...        
Flashlight flashlight = new SomeFlashlight();
flashlight.setBattery(battery);

Мораль. Если нам нужна несколько большая гибкость (внимание! тут и ошибок больше можно допустить) то мы вместо композиции используем более общий случай - агрегацию.

Приведем наглядный пример (его я стырил из Википедии). Комната является частью квартиры, следовательно здесь подходит композиция, потому что комната без квартиры существовать не может. А, например, мебель не является неотъемлемой частью квартиры, но в то же время, квартира содержит мебель, поэтому следует использовать агрегацию.

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

Напомню, что есть и другие методы инъекции зависимостей, в потому продолжение следует (простая фабрика)...

вторник, 12 июля 2011 г.

Java for fun: Что такое Dependency injection, Inversion of Control и почему это возникло. Часть #2

Привет. Уже давненько я писал первую часть поста, видимо пришло время перейти к практической части.

Рассматривать будем 4 случая инъекции:
- инъекция через конструктор
- инъекция через setter
- подготовка объекта с помощью простой фабрики
- подготовка объекта с помощью IoC контейнера написанного собственноручно

Действующие лица:
- какой-то фонарик - class SomeFlashlight
- описание фонарика - integface Flashlight
- описание батарейки - interface Battery
- китайская батарейка - class ChinaBattery

Скажу сразу исходники этого примера можно качнуть тут.

Батарейка - с ней все просто, она дает некоторое напряжение (true) или не дает его (false), если севшая.

package constructor.battery;

public interface Battery {

    boolean getVoltage();

}

По задумке батарейка разряжается от последовательных вызовов ее метода getVoltage, что реализовано в китайской батарейке

package constructor.battery;

public class ChinaBattery implements Battery {

    private int power = 5; 
 
    @Override
    public boolean getVoltage() {
        if (power > 0) {
            power--;
            return true;
        }
        return false;
    }

}

Так как она китайская, то садится за 5 раз.

Идем дальше - интерфейс фонарик

package constructor.flashlight;

public interface Flashlight {

    void swithOn();
 
    void swithOff();

    boolean isShines();

}

Фонарик может включаться (swithOn) и выключаться (swithOff) а светится он или нет, мы узнаем с помощью метода isShines, возвращающего true/false.

Если посмотреть на реализацию, то станет видно, что свет горит (isShines = true) только, если батарейка есть + если она дает заряд. При включении с батарейки снимается заряд, что равносильно ее разрядке, если вспомнить реализацию китайской одноразки.

package constructor.flashlight;

import constructor.battery.Battery;

public class SomeFlashlight implements Flashlight {

    private Battery battery;
    private boolean swithOn;
 
    public SomeFlashlight(Battery battery) {
        this.battery = battery;
        this.swithOn = false;
    }

    @Override
    public boolean isShines() {
        return (battery != null) && swithOn;
    }

    @Override
    public void swithOn() {
        if (!swithOn && battery != null) {
            swithOn = battery.getVoltage();    
        }   
    }

    @Override
    public void swithOff() {
        swithOn = false;
    }

}

Вот тест, демонстрирующий работу фонарика

package constructor;

import static org.junit.Assert.*;
import constructor.battery.Battery;
import constructor.battery.ChinaBattery;
import constructor.flashlight.Flashlight;
import constructor.flashlight.SomeFlashlight;

import org.junit.Test;

public class TestBaterry {
  
    class DisposableBattery implements Battery{

        private boolean full = true;
  
        @Override
        public boolean getVoltage() {   
            if (full) {
                full = false;
                return true;
            }
            return false;
        }
    }
 
    @Test
    public void testDischargeNewBattery() {      
        Battery battery = new DisposableBattery();
  
        Flashlight flashlight = new SomeFlashlight(battery);  
        assertFalse(flashlight.isShines());
  
        flashlight.swithOn();  
        assertTrue(flashlight.isShines());
  
        flashlight.swithOff();  
        assertFalse(flashlight.isShines());
  
        flashlight.swithOn();  
        assertFalse(flashlight.isShines());  
    }
 
    @Test
    public void testBadBattery() {      
        Battery battery = new Battery(){
            @Override
            public boolean getVoltage() {   
                 return false;
            }
        };
  
        Flashlight flashlight = new SomeFlashlight(battery);  
        assertFalse(flashlight.isShines());
  
        flashlight.swithOn();  
        assertFalse(flashlight.isShines());   
    }
 
    @Test
    public void testNoGetPowerIfDoubleSwithOn() {      
        Battery battery = new DisposableBattery();
  
        Flashlight flashlight = new SomeFlashlight(battery);  
        assertFalse(flashlight.isShines());
  
        flashlight.swithOn();  
        assertTrue(flashlight.isShines());
  
        flashlight.swithOn();  
        assertTrue(flashlight.isShines());   
    }
  
    @Test 
    public void testNoBatteryNoLight() {  
        Flashlight flashlight = new SomeFlashlight(null);
  
        assertFalse(flashlight.isShines());
  
        flashlight.swithOn();
  
        assertFalse(flashlight.isShines()); 
    }
 
    @Test
    public void integrationTestGetPowerFormNewChinaBattery() {    
        Battery battery = new ChinaBattery();
        
        Flashlight flashlight = new SomeFlashlight(battery);
  
        assertFalse(flashlight.isShines());
  
        flashlight.swithOn();
  
        assertTrue(flashlight.isShines()); 
    }
 
}

Если посмотреть на каждый тест, то тут что происходит
Battery battery = ...
Flashlight flashlight = new SomeFlashlight(battery);
...

То есть вставляется через конструктор какая-то батарейка. Внутри фонарика все равно какая это будет батарейка, лишь бы соответствовала интерфейсу

public class SomeFlashlight implements Flashlight {

    private Battery battery;
    ...
 
    public SomeFlashlight(Battery battery) {
        this.battery = battery;
        ...
    }
    ...     

Конструктор фонарика мило подставит любую батарейку (которая, implements Battery) в свое поле. Это позволит в дальнейшем пользоваться из фонарика всеми методами, объявленными в интерфейсе Battery

public interface Battery {

    boolean getVoltage();

}

Вот так

public class SomeFlashlight implements Flashlight {
    ...
    @Override
    public void swithOn() {
        if (!swithOn && battery != null) {
            swithOn = battery.getVoltage();    
        }   
    }
...

Любая батарейка (то есть любой класс, главное, чтобы implements Battery) может быть использована фонариком. Фонарик при включении (swithOn()) попросит у батарейки энергии (battery.getVoltage()).

Вообще, так это полиморфизм на интерфейсах - результат выполнения battery.getVoltage() будет зависеть от того, что находится в battery.

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

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

Если с хлебом это прокатило, то с фонариком это кажется немного нелогичным. Мы привыкли видеть фонарики в которых можно заменять батарейки, как только те сели. Тут нам поможет другой способ инъекции зависимости - с помощью setter.

Но об этом в следующей серии "инъекция через setter"...

пятница, 6 августа 2010 г.

Java for fun: Что такое Dependency injection, Inversion of Control и почему это возникло. Часть #1

Сегодня я общался с чудесным человеком, и имел честь ему рассказывать, как я понимаю эту всю хрень, чаще именуемую как dependency injection. Что такое Dependency injection? Как реализуется Dependency injection в Java? Что такое IoC контейнер (из Spring Framework)? Что такое Java Reflection? Почему шаблон проектирования Простая фабрика называется именно так, а не иначе? И много другого вы узнаете из этого поста. Пишу его, пока свежа память. Читаем дальше...