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


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

понедельник, 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"...

воскресенье, 3 октября 2010 г.

Шаблоны: Strategy и Template method или мухи к мухам, котлеты к котлетам.

Strategy и Template method. Как оказалось я не раз использовал эти идеи, но только вчера из книги Мартина я связал этот свой опыт с двумя новыми названиями. Погуглив немного сегодня я понял, что простого описания не так просто найти. Попробую на пальцах (т.е. без кода) объяснить что понимаю сам. Что получится фиг его знает, но выговориться мне явно надо. Итак начнем (т.е. читать дальше)...

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

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

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