Прошлый пост про Liskov, как говорится, "взорвал мой директ", поэтому на этой неделе расскажу про ещё два популярных принципа.
Сегодня про букву D из SOLID — Dependency Inversion. Что это, и чем отличается от Dependency injection и Inversion of Control. Понимание пригодится на собеседованиях, при чтении статей по дизайну и архитектуре.
Будем разбираться на простом примере: класс
Service записывает логи в файл через класс FileLogger:class FileLogger {…}
class Service {
FileLogger logger=new FileLogger();
}
Сделаем код чуть лучше с помощью разных принципов:1️⃣ Dependency injection
— компоненты создаются не внутри класса, а где-то в другом месте.
Как реализовать: перенести инициализацию логгера в конструктор или сеттер:
class Service {
FileLogger logger;
Service (FileLogger logger) {
this.logger=logger;
}
}
✅ Класс занимается только своей бизнес-логикой✅ Можно вынести всю конфигурацию в одно место. Или спихнуть часть забот фреймворку, например, Spring
⚔️Историческая справка
Когда Spring ещё не был популярен, в проектах использовался паттерн Service Locator.
Суть: компоненты создаются в классе ServiceLocator, а другие классы получают к ним доступ через статические методы:
class ServiceLocator {
static Logger logger = …
static Logger getLogger() {
return logger;
}
}
class Service {
Logger logger=ServiceLocator.getLogger();
}
2️⃣ Dependency invertion (D из SOLID)▫️Абстракции не должны зависеть от деталей. Детали должны зависеть от абстракций
▫️Модули верхнего уровня не должны зависеть от модулей нижнего уровня. Оба должны зависеть от абстракции
Как реализовать: использовать интерфейс логгера, а не конкретный класс
interface Logger {…}
class FileLogger implements Logger {…}
class Service {
Logger logger=new FileLogger();
}
✅ Интерфейс проще использовать, так как методов меньше✅ Реализацию легко заменить
✅ Оба класса проще тестировать
Термин "абстракция" используется, потому что SOLID не привязан только к джаве. Группу методов можно выделить в интерфейс, в абстрактный класс и даже в обычный класс. Но интерфейс — наилучший вариант
3️⃣ IoC - Inversion of Control
В маленьких программах жизнь начинается в методе
main. Программист создаёт объекты и вызывает их методы, все шаги явно прописаны.Inversion of Control — это когда ход выполнения программы задаёт фреймворк. Например, Spring создаёт объекты, принимает запросы и не даёт программе завершиться.
Как реализовать: использовать аннотации фреймворка
@Component class FileLogger {…}
@Component class Service {
@Autowired
FileLogger logger;
}
✅ Меньше скучного кода✅ Низкая связность — код легко читать, менять и тестировать
Резюме:
🔸Dependency injection — класс не создаёт компоненты напрямую, они передаются через конструктор или сеттер
🔸Dependency invertion — класс работает с другими компонентами через интерфейс
🔸Inversion of Control — ход программы задаёт фреймворк
❗️Ответ на вопрос перед постом:
Это словоблудие относится к Dependency injection