Существует три схожих понятия, связанных передачей и управлением зависимостями, в каждом из которых есть слово “инверсия” (inversion) или “зависимость” (dependency):
- IoC – Inversion of Control (Инверсия управления)
- DI – Dependency Injection (Внедрение зависимостей)
- DIP – Dependency Inversion Principle (Принцип инверсии зависимостей)
Подливает масло в огонь рассогласованность использования этих терминов. Так, например, контейнеры иногда называют DI-контейнерами, а иногда IoC-контейнерами. Большинство разработчиков не различает DI и DIP, хотя за каждой из этих аббревиатур скрываются разные понятия.
Inversion of Control (IoC)
Инверсия управления (IoC, Inversion of Control) – это достаточно общее понятие, которое отличает библиотеку от фреймворка. Классическая модель подразумевает, что вызывающий код контролирует внешнее окружение и время и порядок вызова библиотечных методов. Однако в случае фреймворка обязанности меняются местами: фреймворк предоставляет некоторые точки расширения, через которые он вызывает определенные методы пользовательского кода.
Простой метод обратного вызова или любая другая форма паттерна Наблюдатель является примером инверсии. Зная значение понятия IoC становится ясно, что такое понятие как IoC-контейнер лишено смысла, если только данный «контейнер» не предназначен для упрощения создания фрейморков.
Dependency Injection (DI)
Внедрение зависимостей (DI, Dependency Injection) – это механизм передачи классу его зависимостей. Существует несколько конкретных видов или паттернов внедрения зависимостей: внедрение зависимости через конструктор (Constructor Injection), через метод (Method Injection) и через свойство (Property Injection).
class ReportProcessor
{
private readonly IReportSender _reportSender;
// Constuctor Injection: передача обязательной зависимости
public ReportProcessor(IReportSender reportSender)
{
_reportSender = reportSender;
Logger = LogManager.DefaultLogger;
}
// Method Injection: передача обязательных зависимостей метода
public void SendReport(Report report, IReportFormatter formatter)
{
Logger.Info("Sending report...");
var formattedReport = formatter.Format(report);
_reportSender.SendReport(formattedReport);
Logger.Info("Report has been sent");
}
// Property Injection: установка необязательных "инфраструктурных" зависимостей
public ILogger Logger { get; set; }
}
Разные виды внедрения зависимостей предназначены для решения определенных задач. Через конструктор передаются обязательные зависимости класса, без которых работа класса невозможна (IReportSender
- обязательная зависимость класса ReportProcessor
). Через метод передаются зависимости, которые нужны лишь одному методу, а не всем методам класса (IReportFormatter
необходим только методу отправки отчета, а не классу ReportProcessor
целиком). Через свойства должны устанавливаться лишь необязательные зависимости (обычно, инфраструктурные), для которых существует значение по умолчанию (свойство Logger
содержит разумное значение по умолчанию, но может быть заменено позднее).
Очень важно понимать, что DI-паттерны не говорят, что за зависимость передается, к какому уровню она относится, должна ли быть она у этого класса или нет. Это лишь инструмент передачи зависимостей от одного класса другому.
Dependency Inversion Principle (DIP)
Принцип инверсии зависимости говорит о том, к каким видам зависимостей нужно стремиться. Важно, чтобы зависимости класса были понятны и важны вызывающему коду. Зависимости класса должны располагаться на текущем или более высоком уровне абстракции. Другими словами, не любой класс, который требует интерфейс в конструкторе следует принципу инверсии зависимостей:
class ReportProcessor
{
private readonly ISocket _socket;
public ReportProcessor(ISocket socket)
{
_socket = socket;
}
public void SendReport(Report report, IStringBuilder stringBuilder)
{
stringBuilder.AppendFormat(CreateHeader(report));
stringBuilder.AppendFormat(CreateBody(report));
stringBuilder.AppendFormat(CreateFooter(report));
_socket.Connect();
_socket.Send(ConvertToByteArray(stringBuilder));
}
}
Класс ReportProcessor
все еще принимает «абстракцию» в аргументах конструктора - ISocket
, но эта «абстракция» находится на несколько уровней ниже уровня формирования и отправки отчетов. Аналогично дела обстоят и с аргументом метода SendReport
: «абстракция» IStringBuilder
не соответствует принципу инверсии зависимостей, поскольку оперирует более низкоуровневыми понятиями, чем требуется. На этом уровне нужно оперировать не строками, а отчетами.
В результате, в данном примере используется внедрение зависимостей (DI), но данный код не следует принципу инверсии зависимостями (DIP).
Подведем итоги.
Инверсия управления (IoC) говорит об изменении потока исполнения, присуща фреймворкам и функциям обратного вызова и не имеет никакого отношения к управлению зависимостями. Передача зависимостей (DI) - это инструмент передачи классу его зависимости через конструктор, метод или свойство. Принцип инверсии зависимостей (DIP) - это принцип проектирования, который говорит, что классы должны зависеть от высокоуровневых абстракций.
Серж, хороший пост. Корректный )))
ОтветитьУдалитьВот бы на английском - отослал бы ребятам пилящим MSR Orleans.
У них с этим реальные проблемы. Собственно как и у практически всех академиков. Инжинииринг - это не к ним ))))
Там по ссылкам есть и англоязычные варианты. В них букв поболее будет, но идея там такая же.
УдалитьПогодь, а эти MSR Orleans - они же в МС-е? А ты мне пришли свои комменты, а я этим товарищам в личку постучу их их вежливо передам!
Три серьезных темы изложил в 3 строки, класс!
ОтветитьУдалить> Зависимости класса должны располагаться на текущем или более высоком уровне абстракции
Скорее, более низком?
Зависимости должны быть на текущем или более высоком уровне абстракции.
УдалитьЗависимости низкого уровня вредны. Это как если класс Reporter-а будет принимать ISocket. Сокет - это абстракция очень низкого уровня. Поэтому репортер должен зависеть от чего-то более высокоуровневого, например, ICommunicationProxy или что-то такого, а не зависеть от низкоурвевых абстракций, типа сокетов.
Ответил здесь: http://sparethought.wordpress.com/2014/11/28/re-di-vs-dip-vs-ioc/
ОтветитьУдалитьОтветил там же, продублирую здесь:
УдалитьЯ не проводил параллель между IoC и фреймворком, а привел это в качестве одного из примеров. В статье явно говорится, что Наблюдатель является одним из примеров инверсии управления. Очевидно, что наблюдатель не имеет никакого отношения ни к фреймворкам/библиотекам.
Ты быстрее написал пост, чем я прочитал твою ссылку на аглийскую статью. Но теперь всё стало на свои места. Спасибо!
ОтветитьУдалитьPS: к своему стыду скажу, что я раньше не знал, что буква "I" расшифровывается по разному в DI и DIP.
Ну так даже лучше, значит можно проревьюить понятность материала:)
УдалитьСпасибо, Сергей. Вещи на полках сознания наконец-то нашли свои места и перестали дрейфовать с полки на полку.
ОтветитьУдалитьСпасибо за статью, теперь всё встало на свои места в голове!
ОтветитьУдалитьСергей, первая картинка в статье не загружается.
ОтветитьУдалитьАлекс, спасибо. Перезалил картинку.
УдалитьСергей, тогда возникает вопрос определения данных терминов в вики. Если исходить из вашего материала по фаулеру, то в вики полный бред и подмена понятий.
ОтветитьУдалитьТогда возникает вопрос что такое IoC контейнер? И что такое DI контейнер и в чем их принципиальные различия?
ps в том же spring употребляют имеено понятие IoC контейнера, который служит для управления зависимостями. Хотя в определении инверсии управления нет ни слова о зависимостях...
Конечно, подмена понятий. Фаулер напакостил в свое время. Под IoC контейнером можете понимать фреймворк. Под DI контейнером просто контейнер - концентратор зависимостей
УдалитьЕсли в вашей системе все компоненты имеют свои зависимости, то где-то в системе какой-то класс или фабрика должны знать, что внедрять во все эти компоненты. Вот что делает DI-контейнер.
Сергей, тогда возникает вопрос определения данных терминов в вики. Если исходить из вашего материала по фаулеру, то в вики полный бред и подмена понятий.
ОтветитьУдалитьТогда возникает вопрос что такое IoC контейнер? И что такое DI контейнер и в чем их принципиальные различия?
ps в том же spring употребляют имеено понятие IoC контейнера, который служит для управления зависимостями. Хотя в определении инверсии управления нет ни слова о зависимостях...
Игорь, а о какой Вики идёт речь (rus/eng)? Я бегло глянул IOC в англоязычной Вики и ереси там не увидел....
УдалитьЭтот комментарий был удален автором.
УдалитьЯ про русскую вики. Вот цитата:
Удалить"Одной из реализаций IoC является внедрение зависимостей (англ. dependency injection)."
Но ведь это полный абсурд. Как внедрение зависимостей может быть реализацией инверсии управления, когда эта два несвязанных понятия.
Как я понял, под IoC контейнером понимается сам фреймворк.
Тогда в чем отличие IoC контейнера от DI контейнера?
Интересно, что на английской вики понятие IOC Container не упоминается.
УдалитьПричина, почему этот термин может использоваться, исключительно историческое.
Более корректное название для всех Unity, Sprint-ов и прочих autofac-ов - это именно DI-контейнер, хотя в некоторых случаях, что-то типа castle-а могут применяться для реализации декоратора, что можно отнести и к полноценному IoC.
Просто в начале нулевых, когда начался бум на это дело, IoC и DI были синонимами, что довольно прочно укоренилось в головах. Отсюда и путаница.
Статья Фаулера, как и эта статья, это как раз попытка вернуть понятия под контроль, поскольку очень важно, чтобы разные люди вкладывали в одно и то же слово, одно и то же значение (ну или хотя бы приблизительно одно и то же).
Я тоже не могу вкурить, почему Dependency Injection считается реализацией Inversion of Control. Если этому просто историческая причина, почему даже в англоязычной Википедии, которая должна быть, по идее, актуальна и точна, не расставлено всё по своим местам? Напротив, сказано: "Dependency injection is a specific type of IoC using contextualized lookup." (и ссылка на Фаулера).
ОтветитьУдалитьНо ведь вернее было бы сказать, что Dependency Injection — это просто один из удобных (и правильных) способов предоставления внешних зависимостей тем частям кода, которые пишет пользователь фреймворка или системы, построенной по принципу Inversion of Control.
Верно ли это? Или я что-то не понимаю?
Как я понимаю, более простыми словами, DI относится к композиции ваших классов, т.е. с помощью DI вы "комопозите" классы в коде. IoC это как runtime enviroment of your code, например, Spring Framework является IoC
УдалитьСледует учитывать, то IoC контейнер (фреймворк) и принцип IoC - это разные вещи. DI -это действительно одна из реализаций принципа IoC, так что тут Вики не ошибается. Есть, конечно, и другие его реализации; например, паттерн Observer. При этом внутри любых фреймворков, DI используется часто и широко, в этом Вы правы.
УдалитьДа, историческая причина. Так придумал Фаулер. Хотя DI служит и для IoC и для DIP Фаулер заострял внимание только на IoC когда вводил этот термин. https://martinfowler.com/articles/injection.html#InversionOfControl
УдалитьЭто добавляет путаницы, конечно
Фрейм орков?
ОтветитьУдалитьЯ бы еще добавил:
ОтветитьУдалитьIoC может быть реализован
а) без дополнительных сущностей - через DI или Service Locator
б) с дополнительными сущностями через шаблон наблюдатель, через фреймворки, через события и реакцию на них
DIP может быть реализован
а) без дополнительных сущностей через DI и других способов я не знаю
б) с дополнительными сущностями через шаблон фабричный метод и так далее