Разделение: сказано, что если класс B изменяется, то на A это не должно повлиять, и я не могу этого понять, потому что, если я заменю fooB () в классе B на fooB2 (), мне придется изменить метод переопределения в BService. что, в свою очередь, означает, что мне придется изменить его в классе A
Думаю, как только вы поймете это, вы поймете всю концепцию.
Попытайтесь представить интерфейсы, которые вы предоставляете, как контракты между различными компонентами вашей системы.
Объявляя BService
с помощью метода fooB()
, вы говорите, что любой компонент, который соблюдает этот контракт (например, реализует интерфейс), способен выполнять объявленную работу по-своему по-своему до тех пор, пока это не нарушает договор.
Компонент A
не должен быть интересным в , как BService
выполняет свою работу, поскольку A
достаточно, чтобы знать, что работа будет выполнена.
Тогда вы сможете создать еще одну реализацию BService
, которая могла бы выполнять необходимую работу совершенно по-другому. Вы можете перенастроить ваш IoC
, чтобы добавить новую реализацию в A
, и это все. Вы не изменили A
, но изменили способ его работы.
Давайте рассмотрим другой пример:
Предположим, у вас есть Repository
интерфейс, который может сохранять / извлекать что угодно по некоторому строковому идентификатору (для простоты).
interface Repository {
Object retrieve(String identifier);
void store(String identifier, Object content);
}
И у вас может быть пара компонентов, которые используют этот репозиторий для манипулирования некоторыми данными:
class DocumentStorage {
private int seqNo = 1;
private Repository repository;
public void saveMyDocuments(Iterable<Document> documents) {
for (Document document : documents) {
repository.store("DocumentStorage" + ++seqNo, document);
}
}
}
И
class RuntimeMetrics {
private Repository repository;
public void saveFreeMemoryAmount() {
repository.store("MEM", Runtime.getRuntime().freeMemory());
}
}
Теперь эти компоненты понятия не имеют о том, как хранилище будет сохранять документы, они просто знают, что так и будет.
И вы можете реализовать хранилище в памяти:
class InMemoryRepository implements Repository {
private final java.util.Map<Integer, Object> mem = new java.util.HashMap<>();
@Override
Object retrieve(Integer identifier) {
return mem.get(identifier);
}
@Override
void store(Integer identifier, Object content) {
mem.put(identifier, content);
}
}
и жить с этим.
Теперь в какой-то момент времени вы можете решить, что документы слишком важны для хранения в памяти, и вам придется хранить их в файле, базе данных или в другом месте.
Вы реализуете DatabaseRepository
в соответствии с контрактом Repository
, перенастройте свой DI-контейнер и BOOM, ваши документы теперь хранятся в базе данных. Вы ничего не изменили в DocumentStorage
, RuntimeMetrics
, все еще используя InMemoryRepository
для управления его данными.
Аналогичным образом вы можете протестировать DocumentStorage
, заменив Repository
поддельной реализацией вместо запуска всего сервера базы данных.
Это главное преимущество DI
.