Что такое ioc inversion of control инверсия зависимостей и для чего она нужна

Обновлено: 05.07.2024

Что такое Инверсия управления и Внедрение зависимостей (IoС & DI)

Паттерн (шаблон) проектирования — это продуманный способ построения исходного кода программы для решения часто возникающих в повседневном программировании проблем проектирования. Иными словами, это уже придуманное решения, для типичной задачи. При этом паттерн не готовое решение, а просто алгоритм действий, который должен привести к желаемому результату. Но сегодня мы рассмотрим не конкретный паттерн проектирования, а принцип построения объектно-ориентированных приложений Инверсия управления (IoC).

Инверсия управления (Inversion of Control, IoC) это определенный набор рекомендаций, позволяющих проектировать и реализовывать приложения используя слабое связывание отдельных компонентов. То есть, для того чтобы следовать принципам Инверсии управления нам необходимо:

  • Реализовывать компоненты, отвечающие за одну конкретную задачу;
  • Компоненты должны быть максимально независимыми друг от друга;
  • Компоненты не должны зависеть от конкретной реализации друг друга.

Одним из видов конкретной реализации данных рекомендаций является механизм Внедрения зависимостей (Dependency Injection, DI). Он определяет две основные рекомендации:

  • модули верхних уровней не должны зависеть от модулей нижних уровней. Оба типа модулей должны зависеть от абстракций;
  • абстракции не должны зависеть от деталей. Детали должны зависеть от абстракций.

То есть, если у нас будут существовать два связанных класса, то нам необходимо реализовывать связь между ними не напрямую, а через интерфейс. Это позволит нам при необходимости динамически менять реализацию зависимых классов.

Предположим, что мы решили написать свою собственную программу, выполняющую крипто вычисления, другим словом майнер. Любая криптовалюта основана на какой-либо хэш-функции (алгоритме). Предположим, что наша программа будет выполнять вычисления на алгоритме SHA256, для майнинга биткоина. Тогда мы получим следующую связь между классами:

No DI

No DI

Проблема состоит в том, что в настоящее время алгоритмов, на которых основаны крипто валюты достаточно много и их число постоянно увеличивается. Если мы захотим добавить новые алгоритмы для майнинга других крипто валют, нам придется вносить большое количество изменений в сам класс майнера. Чтобы этого избежать, необходимо создать промежуточный интерфейс, от которого будет зависеть майнер и который должны будут реализовывать различные алгоритмы.

DI

DI

Так мы сможем не только разделить ответственность за выполнение конкретных задач между классом майнером и алгоритмами, но и сделаем задел на дальнейшее увеличение количества поддерживаемых алгоритмов.

Существует несколько конкретных паттернов Внедрения зависимостей:

  • Через конструктор (Constructor injection);
  • Через свойство класса (Setter injection);
  • Через аргумент метода (Method injection).

Давайте рассмотрим примеры реализации данных паттернов.

Для начала рассмотрим как можно реализовать данную задачу без Внедрения зависимостей. Мы создадим класс майнер, который будет запускать отдельный поток вычисления хеш-функции.

Думаю сейчас слова IoC, DI, IoC-контейнер, как минимум у многих на слуху. Одни этим активно пользуются, другие пытаются понять, что же это за модные веяния.

На данный момент, на эту тему уже довольно сказано, написано, в том числе и на хабре, но как раз из-за обилия информации сложно найти действительно полезный контент. Кроме того, данные понятия часто смешивают и/или путают. Проанализировав множества материалов я решил изложить вам свое видение предмета.

Теория

Для меня взаимосвязь между IoC и DI такая же как между Agile и Scrum, т.е.
Inversion of Control (инверсия управления) — это некий абстрактный принцип, набор рекомендаций для написания слабо связанного кода. Суть которого в том, что каждый компонент системы должен быть как можно более изолированным от других, не полагаясь в своей работе на детали конкретной реализации других компонентов.
Dependency Injection (внедрение зависимостей) — это одна из реализаций этого принципа (помимо этого есть еще Factory Method, Service Locator).
IoC-контейнер — это какая-то библиотека, фреймворк, программа если хотите, которая позволит вам упростить и автоматизировать написание кода с использованием данного подхода на столько, на сколько это возможно. Их довольно много, пользуйтесь тем, чем вам будет удобно, я продемонстрирую все на примере Ninject.

Практика

Схема взаимодействия клиента с сервисом

Согласно подходу инверсии управления если у нас есть клиент, который использует некий сервис, то он должен делать это не напрямую, а через посредника, своего рода аутсорсинг.

То как технически это будет сделано и определяет каждая из реализаций подхода IoC.
Мы будем использовать DI, на простом примере:
Скажем есть некий класс, который создает расписание, а другой класс его отображает (их как правило много, скажем один для десктоп-приложения, другой для веба и т.д.).
Если бы мы ничего не знали о IoC, DI мы бы написали что-то вроде этого:

Вроде бы все хорошо, код решает поставленную задачу, но что если мы захотим в последствии изменить реализацию менеджера расписаний и/или же иметь несколько таких менеджеров и динамически их заменять. Тогда в последствии нам придется менять и что-то в ScheduleViewer, а значит и снова его тестировать.
К счастью, разработчики, ленивые люди в хорошем смысле этого слова, и не любят делать одно и тоже дважды.

Мы, например, воспользуемся внедрением зависимостей (DI) для того, чтобы разорвать этот клубок стальных ниток — сделаем связь между этими классами более слабой, добавив прослойку в виде интерфейса IScheduleManager. И будем разрешать ее одним из способов техники DI, а именно Constructor Injection (помимо этого есть Setter Injection и Method Injection — если в двух словах, то везде используется интерфейс вместо конкретного класса, например в типе свойства или в типе аргумента метода):

И далее там где мы хотим воспользоваться нашим классом для отображения расписания мы пишем:

Вот уже почти идеально, но что если у нас много различных ScheduleViewer, разбросанных по проекту, которые использует всегда именно ScheduleManager (придется его руками каждый раз создавать) и/или мы хотим как-либо настроить поведение, так что бы в одной ситуации везде использовать ScheduleManager, а в другой скажем AnotherScheduleManager и т.д.
Решить эту проблему как раз и призваны IoC-контейнеры.

IoC-контейнеры

Они помогают уменьшить количество рутины, позволяя задать соответствие между интерфейсом и его конкретной реализацией, чтобы потом везде этим пользоваться.
Как я уже говорил выше, мы будем рассматривать это на примере Ninject —
1. Сначала мы создаем конфигурацию контейнера:

Теперь везде где требуется IScheduleManager будет подставляться ScheduleManager.
2. Создаем сам контейнер, указывая его конфигуратор:

Контейнер сам создаст экземпляр класса ScheduleManager, вызовет конструктор ScheduleViewer и подставит в него свежесозданный экземпляр ScheduleManager.

Что такое Инверсия управления и Внедрение зависимостей (IoС & DI)

Паттерн (шаблон) проектирования — это продуманный способ построения исходного кода программы для решения часто возникающих в повседневном программировании проблем проектирования. Иными словами, это уже придуманное решения, для типичной задачи. При этом паттерн не готовое решение, а просто алгоритм действий, который должен привести к желаемому результату. Но сегодня мы рассмотрим не конкретный паттерн проектирования, а принцип построения объектно-ориентированных приложений Инверсия управления (IoC).

Инверсия управления (Inversion of Control, IoC) это определенный набор рекомендаций, позволяющих проектировать и реализовывать приложения используя слабое связывание отдельных компонентов. То есть, для того чтобы следовать принципам Инверсии управления нам необходимо:

  • Реализовывать компоненты, отвечающие за одну конкретную задачу;
  • Компоненты должны быть максимально независимыми друг от друга;
  • Компоненты не должны зависеть от конкретной реализации друг друга.

Одним из видов конкретной реализации данных рекомендаций является механизм Внедрения зависимостей (Dependency Injection, DI). Он определяет две основные рекомендации:

  • модули верхних уровней не должны зависеть от модулей нижних уровней. Оба типа модулей должны зависеть от абстракций;
  • абстракции не должны зависеть от деталей. Детали должны зависеть от абстракций.

То есть, если у нас будут существовать два связанных класса, то нам необходимо реализовывать связь между ними не напрямую, а через интерфейс. Это позволит нам при необходимости динамически менять реализацию зависимых классов.

Предположим, что мы решили написать свою собственную программу, выполняющую крипто вычисления, другим словом майнер. Любая криптовалюта основана на какой-либо хэш-функции (алгоритме). Предположим, что наша программа будет выполнять вычисления на алгоритме SHA256, для майнинга биткоина. Тогда мы получим следующую связь между классами:

No DI

No DI

Проблема состоит в том, что в настоящее время алгоритмов, на которых основаны крипто валюты достаточно много и их число постоянно увеличивается. Если мы захотим добавить новые алгоритмы для майнинга других крипто валют, нам придется вносить большое количество изменений в сам класс майнера. Чтобы этого избежать, необходимо создать промежуточный интерфейс, от которого будет зависеть майнер и который должны будут реализовывать различные алгоритмы.

DI

DI

Так мы сможем не только разделить ответственность за выполнение конкретных задач между классом майнером и алгоритмами, но и сделаем задел на дальнейшее увеличение количества поддерживаемых алгоритмов.

Существует несколько конкретных паттернов Внедрения зависимостей:

  • Через конструктор (Constructor injection);
  • Через свойство класса (Setter injection);
  • Через аргумент метода (Method injection).

Давайте рассмотрим примеры реализации данных паттернов.

Для начала рассмотрим как можно реализовать данную задачу без Внедрения зависимостей. Мы создадим класс майнер, который будет запускать отдельный поток вычисления хеш-функции.

Внедрение зависимостей (DI)

Для начала добавим интерфейс для алгоритмов поиска хешей.

Создадим новый класс алгоритма Ethash и изменим существующий SHA256.

Внедрение зависимостей через конструктор

Изменим класс майнер следующим образом.

Как видите, теперь майнер не зависит от конкретного алгоритма, а принимает только интерфейс как аргумент конструктора.

Теперь нам немного нужно изменить вызов майнера в консольном приложении.


Внедрение зависимостей через свойство


Внедрение зависимостей через аргумент метода


IoC-контейнер

В заключение хочется сказать, что IoC, DI — очень полезный механизм, но и как любой другой инструмент его нужно использовать только там, где он действительно необходим и принесет больше пользы, чем вреда. Ведь его реальное внедрение в небольшое консольное приложение, в котором вряд ли что-то будет меняться только усложнит архитектуру и понимание кода, а также увеличит вероятность совершения ошибки. В то же время если это серьезный крупный проект, где пожелания заказчика часто изменчивы и противоречивы, данный механизм может серьезно упростить жизнь разработчикам.

Рекомендую также изучить статью SOLID в объектно-ориентированном программировании. Инверсия управления входит в набор этих принципов, поэтому целесообразно изучить остальные. А еще подписывайтесь на группу ВКонтакте, Telegram и YouTube-канал. Там еще больше полезного и интересного для программистов.

IoC, DI, IoC-контейнер — Просто о простом

Думаю сейчас слова IoC, DI, IoC-контейнер, как минимум у многих на слуху. Одни этим активно пользуются, другие пытаются понять, что же это за модные веяния.

На данный момент, на эту тему уже довольно сказано, написано, в том числе и на хабре, но как раз из-за обилия информации сложно найти действительно полезный контент. Кроме того, данные понятия часто смешивают и/или путают. Проанализировав множества материалов я решил изложить вам свое видение предмета.

Теория

Для меня взаимосвязь между IoC и DI такая же как между Agile и Scrum, т.е.
Inversion of Control (инверсия управления) — это некий абстрактный принцип, набор рекомендаций для написания слабо связанного кода. Суть которого в том, что каждый компонент системы должен быть как можно более изолированным от других, не полагаясь в своей работе на детали конкретной реализации других компонентов.
Dependency Injection (внедрение зависимостей) — это одна из реализаций этого принципа (помимо этого есть еще Factory Method, Service Locator).
IoC-контейнер — это какая-то библиотека, фреймворк, программа если хотите, которая позволит вам упростить и автоматизировать написание кода с использованием данного подхода на столько, на сколько это возможно. Их довольно много, пользуйтесь тем, чем вам будет удобно, я продемонстрирую все на примере Ninject.

Практика

Схема взаимодействия клиента с сервисом

Согласно подходу инверсии управления если у нас есть клиент, который использует некий сервис, то он должен делать это не напрямую, а через посредника, своего рода аутсорсинг.

То как технически это будет сделано и определяет каждая из реализаций подхода IoC.
Мы будем использовать DI, на простом примере:
Скажем есть некий класс, который создает расписание, а другой класс его отображает (их как правило много, скажем один для десктоп-приложения, другой для веба и т.д.).
Если бы мы ничего не знали о IoC, DI мы бы написали что-то вроде этого:

Вроде бы все хорошо, код решает поставленную задачу, но что если мы захотим в последствии изменить реализацию менеджера расписаний и/или же иметь несколько таких менеджеров и динамически их заменять. Тогда в последствии нам придется менять и что-то в ScheduleViewer, а значит и снова его тестировать.
К счастью, разработчики, ленивые люди в хорошем смысле этого слова, и не любят делать одно и тоже дважды.

Мы, например, воспользуемся внедрением зависимостей (DI) для того, чтобы разорвать этот клубок стальных ниток — сделаем связь между этими классами более слабой, добавив прослойку в виде интерфейса IScheduleManager. И будем разрешать ее одним из способов техники DI, а именно Constructor Injection (помимо этого есть Setter Injection и Method Injection — если в двух словах, то везде используется интерфейс вместо конкретного класса, например в типе свойства или в типе аргумента метода):

И далее там где мы хотим воспользоваться нашим классом для отображения расписания мы пишем:

Вот уже почти идеально, но что если у нас много различных ScheduleViewer, разбросанных по проекту, которые использует всегда именно ScheduleManager (придется его руками каждый раз создавать) и/или мы хотим как-либо настроить поведение, так что бы в одной ситуации везде использовать ScheduleManager, а в другой скажем AnotherScheduleManager и т.д.
Решить эту проблему как раз и призваны IoC-контейнеры.

IoC-контейнеры

Они помогают уменьшить количество рутины, позволяя задать соответствие между интерфейсом и его конкретной реализацией, чтобы потом везде этим пользоваться.
Как я уже говорил выше, мы будем рассматривать это на примере Ninject —
1. Сначала мы создаем конфигурацию контейнера:

Теперь везде где требуется IScheduleManager будет подставляться ScheduleManager.
2. Создаем сам контейнер, указывая его конфигуратор:

Контейнер сам создаст экземпляр класса ScheduleManager, вызовет конструктор ScheduleViewer и подставит в него свежесозданный экземпляр ScheduleManager.

1) Инверсия зависимости это принцип объектно-ориентированного программирования, который служит для уменьшения связанности слоёв сложных приложений. Принцип заключается в том что каждый слой приложение работает с нижестоящим слоем не на прямую, а опосредованно(через слой абстракций). В результате это позволяет легко и безболезненно заменить реализацию каждого из слоёв на новую, при этом не затронув и работу вышестоящих слоёв.

2) DI контейнер это реализация, которая позволяет применить принцип инверсии зависимостей. Она знает о всех интерфейсах и их реализациях в системе и умеет их сопоставлять. Перед началом работы с контейнером необходимо зарегистрировать известные типы и их сопоставления(интерфейс-->реализация).

3) Scope это минимальный квант времени жизни экземпляра реализации интефейса. Другими словами Scope определяет когда необходимо создать новый экземпляр реализации, а когда можно использовать старый(ранее использованный экземпляр). Основные виды Scope:
1. Transient - создаётся новый экземпляр на каждый запрос к контейнеру
2. Thread - создаётся новый экземпляр на каждый поток
3. Single(Singleton) - всегда один экземпляр
.
Также нужно отметить что большинство реализаций DI контейнеров, позволяют объявить собственный Scope и задать ему собственную поведенческую модель.

4) Castle Windsor, Autofac, Ninject, Unity. (на самом деле их очень много, но если кандидат назовёт хотя-бы 2-3 то это хорошо)

5) Вопрос с подвохом. Правильный ответ - "нету такого". Ещё лучше если кандидат скажет - "в зависимости от необходимостей конкретного проекта." . А совсем прекрасно если он скажет второе и приведёт пример в котором один контейнер будет более выигрышно смотреться нежели другой.

Реализация без инверсия управления

Так же мы создадим класс SHA256, который как раз и будет отвечать за нахождения хеша.

Теперь нам только и осталось создать экземпляр нашего майнера и запустить процесс майнинга.

В результате мы получим следующее:

Result no DI

Result no DI

Но как и упоминалось ранее, если мы захотим добавить новый алгоритм нам потребуется внести достаточно много изменений в код нашего класса майнера. Давайте внесем изменения в наши классы так, чтобы сделать их более гибкими и универсальными.

Читайте также: