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


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

вторник, 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"...

13 комментариев:

  1. Ты мега-понятно объясняешь! И пример суперский!

    ОтветитьУдалить
  2. Спасибо. Бывает иногда как нахлынет, могу часы напролет клепать примерчики для блога...

    ОтветитьУдалить
  3. Только благодаря вашему блогу понял что такое Dependency injection, Inversion of Control. Как оказалось за этими неочевидными словами скрываются простые и понятные принципы.
    Спасибо.

    ОтветитьУдалить
  4. Спасибо и вам за отзыв. Но есть одно но - не верьте мне на слово. Экспериментируйте! А потом напишите в своем блоге о вашем опыте, о том как вы понимаете эту тему. Именно тогда вы начнете чувствовать предмет, а не только знать.
    Успехов!

    ОтветитьУдалить
  5. мега круто чувак)) сенкс

    ОтветитьУдалить
  6. Зачетный примерчик. Спасибо огромное. Успехов...

    ОтветитьУдалить
  7. Большое спасибо! Классный пример. Всё доходчиво объяснено.

    ОтветитьУдалить
  8. Спасибо! Очень понятно объяснили то, что слишком заумно везде подается, особенно, когда с наскоку показывают на Spring.
    И Понравились примеры с батарейками ;)

    ОтветитьУдалить
  9. Этот комментарий был удален автором.

    ОтветитьУдалить
  10. Этот комментарий был удален автором.

    ОтветитьУдалить