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


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

среда, 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);

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

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

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

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

2 комментария:

  1. Прикольно написано, но справедливости ради (но не ради наглядности), с конструктора по умолчанию this.swithOn = false; можна смело убрать, поле swithOn у нас примитив, он и так false

    ОтветитьУдалить
  2. Спасибо за комментарий. Обычно я так и делал. Но сейчас, для наглядности и чтобы об код меньше спотыкались коллеги (которые не знают этого) я стараюсь объявлять все поля класса к конструкторе, даже если они примитивы.

    Важно, чтобы об твой код не спотыкались твои коллеги. Ибо если им что-то не очевидно - они с большей вероятностью внесут в твой код ошибку. Работаем ведь в командах.

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