Скажу сразу исходники этого примера можно качнуть тут.
Начнем как всегда с интерфейсов. Описание батарейки - не менялся:
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
ОтветитьУдалитьСпасибо за комментарий. Обычно я так и делал. Но сейчас, для наглядности и чтобы об код меньше спотыкались коллеги (которые не знают этого) я стараюсь объявлять все поля класса к конструкторе, даже если они примитивы.
ОтветитьУдалитьВажно, чтобы об твой код не спотыкались твои коллеги. Ибо если им что-то не очевидно - они с большей вероятностью внесут в твой код ошибку. Работаем ведь в командах.