Скажу сразу исходники этого примера можно качнуть тут.
Чтобы не дать возможность никому создавать фонарик мы сделаем его конструктор 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. Читаем тут....
Спасибо за все статьи, читаются на одном дыхании
ОтветитьУдалитьПолезно! Спасибо! :)
ОтветитьУдалитьПросто отличный цикл постов. Спасибо.
ОтветитьУдалитьМожет я чего-то недопонял, но возник такой вопрос:
Вообщем-то понятно что никто не знает о том, какие батарейки выпускает фабрика батареек. Но раз она фабрика, то и выпускать могла бы не 1 тип батареек, тогда каким-то образом при обращении к ней, мы должны сказать фонарик с каким типом батареек нам необходим в данный момент. Но тогда наша абстракия рушится и фабрика фонариков напрямую управляет фабрикой батарей, говоря что ей конкретно нужно. Вот собственно можно ли так делать в рамках данного паттерна? И если нет, то проясните пожалуйста этот момент.
А так еще раз спасибо. С нетерпением жду продолжения.
Этот комментарий был удален автором.
ОтветитьУдалитьПривет.
ОтветитьУдалитьСкажу сразу, что пока речь не идет об шаблонах проектирования. Так, размышления на тему зависимостей. Кто-то считает Простую Фабрику шаблоном, кто-то нет.
Что касается вашего вопроса, то не вижу причин не сделать фабрику батареек конфигурируемой в соответствии с какими-то входными критериями. Вопрос другой, как фабрика фонариков будет определять эти критерии?
Если фабрика фонариков так же будет отталкиваться от того, что есть несколько видов фонариков, каждый из которых требует свой тип батареек (кому 777 подойдет, а кому Алкалайн нужен), то речь уже идет о семействе взаимосвязанных объектов, а тут на помощь может прийти Абстрактная Фабрика (это уже полноценный шаблон).
Вообще, шаблоны стоит применять не потому, что их можно просто применить в коде. Но в ходе рефакторинга, перед подготовкой кода к внесению нового изменения. Шаблоны проектирования помогают инкапсулировать будущие изменения. Решая эту цель, занимаясь рефакторингом, можно прийти к тому или иному шаблону.
Экспериментируйте.
Много букав, которые с удовольствие осилил)) Спасибо!
ОтветитьУдалитьНасколько понимаю, следует ещё "подготовка объекта с помощью IoC контейнера написанного собственноручно" - вот прям с нетерпением:)
Спасибо за отзыв. Продолжение обязательно будет. Более того их количество прямо пропорционально числу отзывов - если вам, читатель, полезно то буду писать еще.
ОтветитьУдалитьДа-да-да, ждем-с IoC! Очень интересно пишите
ОтветитьУдалитьПосты реально очень полезны, т.к. разжеваны так, что "бабушке въехать" теперь - ничего не стоит:)
ОтветитьУдалитьПосты супер, жду с нетерпением продолжения. Тема очень интересная. Много статей перелопатил, и только у вас все понятно с первого раза. Осталось только немного попрактиковаться.
ОтветитьУдалитьМетоды обоих фабрик следует сделать статическими:
ОтветитьУдалить1) Поможет избежать ненужного создания экземпляров классов фабрик.
2) Сделает клиентский код читабельнее.
Будет
Flashlight flashlight = FlashLightFactory.getFlashlight();
Вместо
Flashlight flashlight = new FlashLightFactory().getFlashlight();
Так же, для того что бы уберечься от теоретической возможности того что клиент будет по-прежнему пытаться вызывать getFlashlight() на экземпляре, можно дописать приватные конструкторы фабрикам.
PS: за статьи спасибо
Андрей.
Спасибо за комментарий.
ОтветитьУдалитьТочно! В этом примере лучше сделать методы статическими, а конструктор приватным.
Есть, правда, один недостаток, который не нравится мне в статике - в будущем это помешало бы мокать фабрику в коде, который ее собрался бы использовать. Мне пришлось бы добавить интерфейс к фабрике и убрать статику.
Как вы справляетесь в таком случае?
спасибо
ОтветитьУдалитьНе знаю, обитает ли тут еще автор, но надеюсь что да)
ОтветитьУдалитьКак я понял, в Dependency Injection с конструктором и геттером, я могу взять мой класс SomeFlashLight, упаковать его в библиотеку вместе с интерфейсами FlashLight и Battery, и подарить эту библиотеку кому-нибудь другому, кто может написать класс, например, GermanBattery, и абсолютно спокойно использовать мой SomeFlashLight.
Однако в примере с этой простой фабрикой, для использования скомпилированного SomeFlashLight другим человеком, ему обязательно потребуется так же скомпилированный FlashLightFactory. Для которой в свою очередь потребуется BattaryFactory, для которой в свою очередь потребуется скомпилировать класс ChinaBattery.
Но таким образом, развязать зависимость в данном случае не получилось, или я что-то проглядел?
Был бы очень благодарен за ответ.
Блин, не правильно написал. Скорее так:
Удалитьдля компиляции SomeFlashLight потребуется так же скомпилировать FlashLightFactory. (и все остальные упомянутые в комментарии классы)
Алекс, именно. Только через FlashLightFactory мы можем инстанциировать, а она зависима от реализаций. Потому если хочешь разделять, то надо приоткрыть SomeFlashlight и сделать его конструктор им сам класс публичным.
УдалитьЭтот комментарий был удален автором.
ОтветитьУдалить