Скажу сразу исходники этого примера можно качнуть тут.
Начнем как всегда с интерфейсов. Описание батарейки - не менялся:
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);
Мораль. Если нам нужна несколько большая гибкость (внимание! тут и ошибок больше можно допустить) то мы вместо композиции используем более общий случай - агрегацию.
Приведем наглядный пример (его я стырил из Википедии). Комната является частью квартиры, следовательно здесь подходит композиция, потому что комната без квартиры существовать не может. А, например, мебель не является неотъемлемой частью квартиры, но в то же время, квартира содержит мебель, поэтому следует использовать агрегацию.
Возвращаясь к фонарикам: обычный фонарик со сменными батарейками - агрегация; светодиодный фонарик, который встроен в зажигалку - композиция.
Напомню, что есть и другие методы инъекции зависимостей, в потому продолжение следует (простая фабрика)...
Прикольно написано, но справедливости ради (но не ради наглядности), с конструктора по умолчанию this.swithOn = false; можна смело убрать, поле swithOn у нас примитив, он и так false
ОтветитьУдалитьСпасибо за комментарий. Обычно я так и делал. Но сейчас, для наглядности и чтобы об код меньше спотыкались коллеги (которые не знают этого) я стараюсь объявлять все поля класса к конструкторе, даже если они примитивы.
ОтветитьУдалитьВажно, чтобы об твой код не спотыкались твои коллеги. Ибо если им что-то не очевидно - они с большей вероятностью внесут в твой код ошибку. Работаем ведь в командах.