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


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

суббота, 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. Читаем тут....

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

  1. Спасибо за все статьи, читаются на одном дыхании

    ОтветитьУдалить
  2. Полезно! Спасибо! :)

    ОтветитьУдалить
  3. Просто отличный цикл постов. Спасибо.

    Может я чего-то недопонял, но возник такой вопрос:
    Вообщем-то понятно что никто не знает о том, какие батарейки выпускает фабрика батареек. Но раз она фабрика, то и выпускать могла бы не 1 тип батареек, тогда каким-то образом при обращении к ней, мы должны сказать фонарик с каким типом батареек нам необходим в данный момент. Но тогда наша абстракия рушится и фабрика фонариков напрямую управляет фабрикой батарей, говоря что ей конкретно нужно. Вот собственно можно ли так делать в рамках данного паттерна? И если нет, то проясните пожалуйста этот момент.

    А так еще раз спасибо. С нетерпением жду продолжения.

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

    ОтветитьУдалить
  5. Привет.

    Скажу сразу, что пока речь не идет об шаблонах проектирования. Так, размышления на тему зависимостей. Кто-то считает Простую Фабрику шаблоном, кто-то нет.

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

    Если фабрика фонариков так же будет отталкиваться от того, что есть несколько видов фонариков, каждый из которых требует свой тип батареек (кому 777 подойдет, а кому Алкалайн нужен), то речь уже идет о семействе взаимосвязанных объектов, а тут на помощь может прийти Абстрактная Фабрика (это уже полноценный шаблон).

    Вообще, шаблоны стоит применять не потому, что их можно просто применить в коде. Но в ходе рефакторинга, перед подготовкой кода к внесению нового изменения. Шаблоны проектирования помогают инкапсулировать будущие изменения. Решая эту цель, занимаясь рефакторингом, можно прийти к тому или иному шаблону.

    Экспериментируйте.

    ОтветитьУдалить
  6. Много букав, которые с удовольствие осилил)) Спасибо!
    Насколько понимаю, следует ещё "подготовка объекта с помощью IoC контейнера написанного собственноручно" - вот прям с нетерпением:)

    ОтветитьУдалить
  7. Спасибо за отзыв. Продолжение обязательно будет. Более того их количество прямо пропорционально числу отзывов - если вам, читатель, полезно то буду писать еще.

    ОтветитьУдалить
  8. Да-да-да, ждем-с IoC! Очень интересно пишите

    ОтветитьУдалить
  9. Посты реально очень полезны, т.к. разжеваны так, что "бабушке въехать" теперь - ничего не стоит:)

    ОтветитьУдалить
  10. Посты супер, жду с нетерпением продолжения. Тема очень интересная. Много статей перелопатил, и только у вас все понятно с первого раза. Осталось только немного попрактиковаться.

    ОтветитьУдалить
  11. Методы обоих фабрик следует сделать статическими:
    1) Поможет избежать ненужного создания экземпляров классов фабрик.
    2) Сделает клиентский код читабельнее.
    Будет
    Flashlight flashlight = FlashLightFactory.getFlashlight();
    Вместо
    Flashlight flashlight = new FlashLightFactory().getFlashlight();

    Так же, для того что бы уберечься от теоретической возможности того что клиент будет по-прежнему пытаться вызывать getFlashlight() на экземпляре, можно дописать приватные конструкторы фабрикам.

    PS: за статьи спасибо

    Андрей.

    ОтветитьУдалить
  12. Спасибо за комментарий.

    Точно! В этом примере лучше сделать методы статическими, а конструктор приватным.

    Есть, правда, один недостаток, который не нравится мне в статике - в будущем это помешало бы мокать фабрику в коде, который ее собрался бы использовать. Мне пришлось бы добавить интерфейс к фабрике и убрать статику.

    Как вы справляетесь в таком случае?

    ОтветитьУдалить
  13. Не знаю, обитает ли тут еще автор, но надеюсь что да)
    Как я понял, в Dependency Injection с конструктором и геттером, я могу взять мой класс SomeFlashLight, упаковать его в библиотеку вместе с интерфейсами FlashLight и Battery, и подарить эту библиотеку кому-нибудь другому, кто может написать класс, например, GermanBattery, и абсолютно спокойно использовать мой SomeFlashLight.

    Однако в примере с этой простой фабрикой, для использования скомпилированного SomeFlashLight другим человеком, ему обязательно потребуется так же скомпилированный FlashLightFactory. Для которой в свою очередь потребуется BattaryFactory, для которой в свою очередь потребуется скомпилировать класс ChinaBattery.
    Но таким образом, развязать зависимость в данном случае не получилось, или я что-то проглядел?
    Был бы очень благодарен за ответ.

    ОтветитьУдалить
    Ответы
    1. Блин, не правильно написал. Скорее так:
      для компиляции SomeFlashLight потребуется так же скомпилировать FlashLightFactory. (и все остальные упомянутые в комментарии классы)

      Удалить
    2. Алекс, именно. Только через FlashLightFactory мы можем инстанциировать, а она зависима от реализаций. Потому если хочешь разделять, то надо приоткрыть SomeFlashlight и сделать его конструктор им сам класс публичным.

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

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