Рассматривать будем 4 случая инъекции:
- инъекция через конструктор
- инъекция через setter
- подготовка объекта с помощью простой фабрики
- подготовка объекта с помощью IoC контейнера написанного собственноручно
Действующие лица:
- какой-то фонарик - class SomeFlashlight
- описание фонарика - integface Flashlight
- описание батарейки - interface Battery
- китайская батарейка - class ChinaBattery
Скажу сразу исходники этого примера можно качнуть тут.
Батарейка - с ней все просто, она дает некоторое напряжение (true) или не дает его (false), если севшая.
package constructor.battery;
public interface Battery {
boolean getVoltage();
}По задумке батарейка разряжается от последовательных вызовов ее метода getVoltage, что реализовано в китайской батарейке
package constructor.battery;
public class ChinaBattery implements Battery {
private int power = 5;
@Override
public boolean getVoltage() {
if (power > 0) {
power--;
return true;
}
return false;
}
}Так как она китайская, то садится за 5 раз.
Идем дальше - интерфейс фонарик
package constructor.flashlight;
public interface Flashlight {
void swithOn();
void swithOff();
boolean isShines();
}Фонарик может включаться (swithOn) и выключаться (swithOff) а светится он или нет, мы узнаем с помощью метода isShines, возвращающего true/false.
Если посмотреть на реализацию, то станет видно, что свет горит (isShines = true) только, если батарейка есть + если она дает заряд. При включении с батарейки снимается заряд, что равносильно ее разрядке, если вспомнить реализацию китайской одноразки.
package constructor.flashlight;
import constructor.battery.Battery;
public class SomeFlashlight implements Flashlight {
private Battery battery;
private boolean swithOn;
public SomeFlashlight(Battery battery) {
this.battery = battery;
this.swithOn = false;
}
@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 constructor;
import static org.junit.Assert.*;
import constructor.battery.Battery;
import constructor.battery.ChinaBattery;
import constructor.flashlight.Flashlight;
import constructor.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(battery);
assertFalse(flashlight.isShines());
flashlight.swithOn();
assertTrue(flashlight.isShines());
flashlight.swithOff();
assertFalse(flashlight.isShines());
flashlight.swithOn();
assertFalse(flashlight.isShines());
}
@Test
public void testBadBattery() {
Battery battery = new Battery(){
@Override
public boolean getVoltage() {
return false;
}
};
Flashlight flashlight = new SomeFlashlight(battery);
assertFalse(flashlight.isShines());
flashlight.swithOn();
assertFalse(flashlight.isShines());
}
@Test
public void testNoGetPowerIfDoubleSwithOn() {
Battery battery = new DisposableBattery();
Flashlight flashlight = new SomeFlashlight(battery);
assertFalse(flashlight.isShines());
flashlight.swithOn();
assertTrue(flashlight.isShines());
flashlight.swithOn();
assertTrue(flashlight.isShines());
}
@Test
public void testNoBatteryNoLight() {
Flashlight flashlight = new SomeFlashlight(null);
assertFalse(flashlight.isShines());
flashlight.swithOn();
assertFalse(flashlight.isShines());
}
@Test
public void integrationTestGetPowerFormNewChinaBattery() {
Battery battery = new ChinaBattery();
Flashlight flashlight = new SomeFlashlight(battery);
assertFalse(flashlight.isShines());
flashlight.swithOn();
assertTrue(flashlight.isShines());
}
}Если посмотреть на каждый тест, то тут что происходит
Battery battery = ... Flashlight flashlight = new SomeFlashlight(battery); ...
То есть вставляется через конструктор какая-то батарейка. Внутри фонарика все равно какая это будет батарейка, лишь бы соответствовала интерфейсу
public class SomeFlashlight implements Flashlight {
private Battery battery;
...
public SomeFlashlight(Battery battery) {
this.battery = battery;
...
}
...
Конструктор фонарика мило подставит любую батарейку (которая, implements Battery) в свое поле. Это позволит в дальнейшем пользоваться из фонарика всеми методами, объявленными в интерфейсе Battery
public interface Battery {
boolean getVoltage();
}Вот так
public class SomeFlashlight implements Flashlight {
...
@Override
public void swithOn() {
if (!swithOn && battery != null) {
swithOn = battery.getVoltage();
}
}
...Любая батарейка (то есть любой класс, главное, чтобы implements Battery) может быть использована фонариком. Фонарик при включении (swithOn()) попросит у батарейки энергии (battery.getVoltage()).
Вообще, так это полиморфизм на интерфейсах - результат выполнения battery.getVoltage() будет зависеть от того, что находится в battery.
И это первый вариант инъекции зависимости - через конструктор. Используется она тогда, когда фонарик не может существовать без батарейки. Представь себе такой одноразовый фонарик, в который вмонтирована батарейка и доступа к ней нет. Села батарейка - выкидываешь фонарик.
Это называется композицией, когда части объекта неотъемлемы от него. Вот хлеб с изюмом состоит из муки, воды, соли, изюма - все это разобрать по частям не возможно. Хлеб - это композит. Раз создали используем все вместе.
Если с хлебом это прокатило, то с фонариком это кажется немного нелогичным. Мы привыкли видеть фонарики в которых можно заменять батарейки, как только те сели. Тут нам поможет другой способ инъекции зависимости - с помощью setter.
Но об этом в следующей серии "инъекция через setter"...