Что такое Dependency Injection (DI) и какие есть виды?

Ответ

Dependency Injection (DI, Внедрение зависимостей) — это паттерн проектирования, при котором зависимости объекта предоставляются ему извне, а не создаются внутри. Это делает код слабосвязанным, тестируемым и гибким.

Основные виды DI:

  1. Внедрение через инициализатор (Constructor/Initializer Injection)

    • Зависимости передаются как параметры инициализатора.
    • Преимущество: объект становится неизменяемым (immutable) после создания, зависимость обязательна.
      class DataService {
      private let networkClient: NetworkClient
      init(networkClient: NetworkClient) {
          self.networkClient = networkClient
      }
      func fetchData() { /* использует networkClient */ }
      }
  2. Внедрение через свойство (Property Injection)

    • Зависимость устанавливается через публичное свойство после создания объекта.
    • Преимущество: гибкость, возможность изменять зависимость.
    • Недостаток: объект может временно находиться в невалидном состоянии.
      class ViewController {
      var analyticsService: AnalyticsService? // Опциональная зависимость
      }
      // ... позже
      vc.analyticsService = AnalyticsService()
  3. Внедрение через метод (Method Injection)

    • Зависимость передается как параметр метода, где она требуется.
    • Преимущество: хорош для зависимостей, которые меняются от вызова к вызову.
      class DataProcessor {
      func process(data: Data, using encoder: JSONEncoder) { /* ... */ }
      }

Почему DI важен?

  • Тестируемость: Легко подменить реальную зависимость (сетевой слой, базу данных) на mock или stub.
  • Слабая связанность: Классы не зависят от конкретных реализаций, а от протоколов/абстракций.
  • Повторное использование: Компоненты становятся более независимыми и переиспользуемыми.

В iOS/Swift DI часто реализуют вручную, с помощью контейнеров (Swinject, Dip) или используют встроенные механизмы SwiftUI (@EnvironmentObject, @Environment).

Ответ 18+ 🔞

А, слушай-ка, про эту вашу модульность, блядь! Ну, Dependency Injection, ёпта! Это ж когда ты не сам в себе всё дергаешь, как последний распиздяй, а тебе подают готовенькое, прямо в инициализатор, как в рот, блядь!

Представь, ты — класс. И тебе нужен, ну, скажем, NetworkClient, чтобы в интернет тыкаться. Так вот, мудацкий подход — это внутри себя его создать: self.client = NetworkClient(). И всё, ты с ним сросся, как сиамские близнецы, блядь! А как его теперь подменить, чтобы тесты написать? Да никак, пиздец! Ты же его внутри намертво припаял, сука!

А умный подход — это DI. Тебе говорят: «На, мужик, держи клиента, какой даём». И ты такой: «А, окей». И работаешь с тем, что дали. А дать тебе могут и реальный клиент для продакшена, и заглушку для тестов — да хоть хуй с горы! Ты от этого не зависишь, понимаешь? Слабая связанность, ёбанашка! Это как раз про это.

Основные способы, как эту зависимость впендюрить:

  1. Через инициализатор (Constructor Injection). Самый честный, блядь, способ. Сразу при рождении требуешь: «Без NetworkClient я — ноль!». И тебе его вручают. Объект сразу готов к бою, и никаких опциональных «а вдруг?».

    class DataService {
        private let networkClient: NetworkClient // Зависимость, ёпта!
        init(networkClient: NetworkClient) { // Принимай, не рыпайся!
            self.networkClient = networkClient
        }
        func fetchData() { /* использует того, кого дали */ }
    }

    Красота, блядь! Объект неизменяемый, как скала. Сделали — и забыли.

  2. Через свойство (Property Injection). Это для гибких, но потенциально недоделанных. Создали объект, а потом, через какое-то время, пихаете ему зависимость в публичное свойство. Удобно? Да. Но объект какое-то время может быть в состоянии «ну я вроде есть, но нихуя не могу, потому что analyticsServicenil». Рискованный пиздец, если забыть.

    class ViewController {
        var analyticsService: AnalyticsService? // Может быть, а может и нет, блядь
    }
    // ... а через сто строк кода
    vc.analyticsService = AnalyticsService() // Вот теперь, сука, живи!
  3. Через метод (Method Injection). Самый ситуативный. Когда зависимость нужна только для одного конкретного дела. Зачем тащить её в поля, если можно передать прямо в метод, как аргумент?

    class DataProcessor {
        func process(data: Data, using encoder: JSONEncoder) { /* ... */ } // На, обработай этим!
    }

А нахуя это всё, спрашивается? Да затем, ёпта, что тестируемость становится не пиздёжом, а реальностью. Хочешь проверить логику DataService? Подсунул ей заглушку MockNetworkClient, которая не лезет в сеть, а тупо возвращает готовый JSON — и тестируй на здоровье, блядь! Слабая связанность — классы перестают быть монолитами, их можно тасовать, как карты. Повторное использование — компонент, который ни от кого не зависит, кроме абстракции, можно тырить в другие проекты, и он там приживётся.

В iOS, блядь, можно городить это вручную (просто передавая зависимости по цепочке), можно использовать контейнеры вроде Swinject (это такие банки, где зависимости заранее зарегистрированы, а потом по запросу выдают). А в SwiftUI вообще своя атмосфера — @EnvironmentObject или @Environment. Это тоже, по сути, DI, только под капотом у свифтуюашного фреймворка. В общем, инструментов — овердохуища. Главное — понять принцип, а не тупо библиотеку подключить.