День четыреста семьдесят пятый. #DesignPatterns
Принцип инверсии зависимостей (DIP). Примечание
DI vs. DIP vs. IoC
Существуют три схожих понятия, связанных с передачей зависимостей, в каждом из которых есть слово «инверсия» (inversion) или «зависимость» (dependency):
1.
IoC — Inversion of Control (инверсия управления);
2.
DI — Dependency Injection (внедрение зависимостей);
3.
DIP — Dependency Inversion Principle (принцип инверсии зависимостей).
Эти термины часто используют несогласованно или путают.
1. Inversion of Control
Инверсия управления — это некий абстрактный принцип, набор рекомендаций для написания слабо связанного кода. В обычной программе программист сам решает, в какой последовательности делать вызовы процедур. Но, если используется фреймворк, программист может разместить свой код в определённых точках выполнения (используя функции обратного вызова или подобные механизмы), затем запустить «главную функцию» фреймворка, которая обеспечит всё выполнение и будет вызывать код программиста тогда, когда это будет необходимо. Как следствие, происходит утеря контроля над выполнением кода — это и называется инверсией управления (фреймворк управляет кодом программиста, а не программист управляет фреймворком).
IoC-контейнер – это фреймворк для реализации автоматического внедрения зависимостей. Он управляет созданием, жизненным циклом объектов и внедрением зависимостей в класс, тем самым «изымая» контроль над зависимостями из класса.
2. Dependency Injection
Внедрение зависимостей — это механизм передачи классу его зависимостей:
-
Через конструктор (Constructor Injection) передаются обязательные зависимости класса, без которых работа класса невозможна.
-
Через метод (Method Injection) передаются зависимости, которые нужны лишь одному методу, а не всем методам класса.
-
Через свойство (Property Injection) чаще устанавливаются лишь необязательные зависимости (обычно инфраструктурные), для которых существует значение по умолчанию (например, свойство
Logger
содержит разумное значение по умолчанию, но в некоторых случаях может быть заменено присваиванием другого значения свойству).
Очень важно понимать, что DI-паттерны не говорят о том, как должна выглядеть зависимость, к какому уровню она относится, сколько их должно быть и т. п. Это лишь инструмент передачи зависимостей от одного класса другому.
3. Dependency Inversion Principle
Принцип инверсии зависимости говорит о том, какого вида зависимости необходимо передавать классу извне, а какие зависимости класс должен создавать самостоятельно. Важно, чтобы зависимости класса были понятны и важны вызывающему коду. Зависимости класса должны располагаться на текущем или более высоком уровне абстракции. Другими словами, не любой класс, которого требует интерфейс в конструкторе, следует
принципу инверсии зависимостей.
Например, в класс создания отчётов можно внедрить зависимости от
IStringBuilder
для построения текста отчёта, либо от
ISocket
для отправки отчётов по сети. Но эти абстракции находятся на несколько уровней ниже
уровня формирования и отправки отчетов и не соответствуют принципу инверсии зависимостей, поскольку оперируют более низкоуровневыми понятиями, чем требуется. На этом уровне нужно оперировать не строками и сокетами, а отчетами. В результате в данном примере используется внедрение зависимостей (DI), но данный код не следует принципу инверсии зависимостей (DIP).
Итого
Инверсия управления (IoC) говорит об изменении потока исполнения, присуща фреймворкам и функциям обратного вызова и не имеет прямого отношения к управлению зависимостями. Внедрение зависимостей (DI) — это инструмент передачи классу его зависимости через конструктор, метод или свойство. Принцип инверсии зависимостей (DIP) — это принцип проектирования, который говорит, что классы должны зависеть от высокоуровневых абстракций.
Источник: Тепляков С. "Паттерны проектирования на платформе .NET." — СПб.: Питер, 2015. Глава 21.