В чем разница между Dependency Injection (DI) и Dependency Inversion Principle (DIP)?

«В чем разница между Dependency Injection (DI) и Dependency Inversion Principle (DIP)?» — вопрос из категории Архитектура, который задают на 10% собеседований IOS Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

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

Способы внедрения:

  1. Через инициализатор (Constructor Injection) — наиболее предпочтительный.
  2. Через свойство (Property Injection).
  3. Через метод (Method Injection).

Dependency Inversion Principle (DIP) — это принцип проектирования, пятый принцип SOLID. Он декларирует:

  1. Модули верхнего уровня (политика) не должны зависеть от модулей нижнего уровня (детали). Оба должны зависеть от абстракций (интерфейсов/протоколов).
  2. Абстракции не должны зависеть от деталей. Детали должны зависеть от абстракций.

Взаимосвязь: DI является распространенным механизмом для соблюдения DIP. DIP задает цель (зависимость от абстракций), а DI предоставляет способ её достижения (внедрение этих абстракций).

Пример без DIP/DI (плохо):

class Service { func fetch() { /* ... */ } }

class ViewModel {
    private let service = Service() // Прямая зависимость от конкретного класса
    func load() { service.fetch() }
}

Пример с DIP и DI (хорошо):

// Абстракция (DIP)
protocol DataFetcherProtocol {
    func fetch()
}

// Деталь (DIP)
class NetworkService: DataFetcherProtocol {
    func fetch() { /* сетевой запрос */ }
}
class MockService: DataFetcherProtocol {
    func fetch() { /* тестовые данные */ }
}

// Модуль верхнего уровня (DIP + DI)
class ViewModel {
    private let fetcher: DataFetcherProtocol
    // Внедрение зависимости (DI) через инициализатор
    init(fetcher: DataFetcherProtocol) {
        self.fetcher = fetcher // Зависит от абстракции
    }
    func load() { fetcher.fetch() }
}

// Использование
let realVM = ViewModel(fetcher: NetworkService()) // Продакшен
let testVM = ViewModel(fetcher: MockService())    // Тесты