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


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

понедельник, 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 через конструктор или сеттер не поможет.

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

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

  1. спасибо за статьи про dependency injection, интересно было читать :)

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

    ОтветитьУдалить
  3. Спасибо очень все ясно и доступно!

    ОтветитьУдалить
  4. Спасибо, побольше бы таких материалов от вас =) прекрасная подача!

    ОтветитьУдалить
    Ответы
    1. Спасибо за фидбек.
      С недавнего времени все подобные материалы публикуются в http://juja.com.ua комьюнити

      Удалить