Ответ
Dependency Injection (DI) — это паттерн проектирования, при котором зависимости объекта предоставляются извне, а не создаются внутри самого объекта. Это делает код более тестируемым, гибким и соответствующим принципу инверсии зависимостей (D из SOLID).
Три основных типа DI:
- Constructor Injection — зависимости передаются через инициализатор
- Property Injection — зависимости устанавливаются через свойства
- Method Injection — зависимости передаются как параметры метода
1. Constructor Injection (наиболее предпочтительный):
// Протокол для абстракции
protocol NetworkServiceProtocol {
func fetchData(completion: @escaping (Result<Data, Error>) -> Void)
}
protocol DatabaseServiceProtocol {
func save(data: Data) throws
}
// Конкретные реализации
class NetworkService: NetworkServiceProtocol {
func fetchData(completion: @escaping (Result<Data, Error>) -> Void) {
// Реальная реализация сетевого запроса
}
}
class DatabaseService: DatabaseServiceProtocol {
func save(data: Data) throws {
// Реальная реализация сохранения в БД
}
}
// Класс, получающий зависимости через инициализатор
class DataManager {
private let networkService: NetworkServiceProtocol
private let databaseService: DatabaseServiceProtocol
// Constructor Injection
init(networkService: NetworkServiceProtocol,
databaseService: DatabaseServiceProtocol) {
self.networkService = networkService
self.databaseService = databaseService
}
func fetchAndSave() {
networkService.fetchData { [weak self] result in
switch result {
case .success(let data):
try? self?.databaseService.save(data: data)
case .failure(let error):
print("Error: (error)")
}
}
}
}
// Использование
let networkService = NetworkService()
let databaseService = DatabaseService()
let dataManager = DataManager(
networkService: networkService,
databaseService: databaseService
)
2. Property Injection (когда зависимости опциональны):
class AnalyticsManager {
// Зависимость устанавливается после инициализации
var analyticsService: AnalyticsServiceProtocol?
func trackEvent(_ event: String) {
analyticsService?.track(event: event)
}
}
let manager = AnalyticsManager()
manager.analyticsService = FirebaseAnalyticsService() // Property Injection
3. Method Injection (для временных зависимостей):
class ImageProcessor {
func process(image: UIImage, using filter: FilterProtocol) -> UIImage {
return filter.apply(to: image) // Method Injection
}
}
Реализация DI контейнера (ручная):
class DIContainer {
private var services: [String: Any] = [:]
func register<Service>(_ type: Service.Type, factory: @escaping () -> Service) {
let key = String(describing: type)
services[key] = factory
}
func resolve<Service>(_ type: Service.Type) -> Service? {
let key = String(describing: type)
guard let factory = services[key] as? () -> Service else {
return nil
}
return factory()
}
}
// Использование контейнера
let container = DIContainer()
container.register(NetworkServiceProtocol.self) {
return NetworkService()
}
container.register(DatabaseServiceProtocol.self) {
return DatabaseService()
}
let networkService = container.resolve(NetworkServiceProtocol.self)
Использование фреймворков (Swinject пример):
import Swinject
let container = Container()
container.register(NetworkServiceProtocol.self) { _ in
return NetworkService()
}.inObjectScope(.container) // Singleton scope
container.register(DataManager.self) { resolver in
let networkService = resolver.resolve(NetworkServiceProtocol.self)!
let databaseService = DatabaseService()
return DataManager(
networkService: networkService,
databaseService: databaseService
)
}
let dataManager = container.resolve(DataManager.self)
Преимущества DI:
- Тестируемость — легко подменять реальные сервисы моками
- Гибкость — можно менять реализации без изменения клиентского кода
- Чистая архитектура — соблюдение принципа инверсии зависимостей
- Повторное использование — компоненты становятся более независимыми
Практические рекомендации:
- Всегда используйте протоколы для абстракции зависимостей
- Предпочитайте Constructor Injection там, где это возможно
- Для больших проектов используйте DI-фреймворки (Swinject, Needle)
- Избегайте Service Locator антипаттерна (глобальный доступ к сервисам)
- Документируйте зависимости в инициализаторах
Пример тестирования с DI:
class MockNetworkService: NetworkServiceProtocol {
var shouldFail = false
func fetchData(completion: @escaping (Result<Data, Error>) -> Void) {
if shouldFail {
completion(.failure(MockError.networkError))
} else {
completion(.success(Data()))
}
}
}
class DataManagerTests: XCTestCase {
func testFetchAndSaveSuccess() {
// Arrange
let mockNetworkService = MockNetworkService()
let mockDatabaseService = MockDatabaseService()
let manager = DataManager(
networkService: mockNetworkService,
databaseService: mockDatabaseService
)
// Act
manager.fetchAndSave()
// Assert
XCTAssertTrue(mockDatabaseService.saveWasCalled)
}
} Ответ 18+ 🔞
А, Dependency Injection, ёпта! Ну это ж та самая хуйня, когда ты не создаёшь свои зависимости внутри класса, как последний распиздяй, а тебе их, блядь, аккуратно подсовывают снаружи. Типа, не «я сам себе всё сделаю, я большой», а «на, дружок, держи готовенькое, не еби мозг».
Смотри, есть три основных способа, как эту дичь можно провернуть. И первый, самый правильный, блядь, — это через инициализатор запихнуть. Constructor Injection, называется. Вот смотри, как это выглядит, не обосрись со смеху:
protocol NetworkServiceProtocol {
func fetchData(completion: @escaping (Result<Data, Error>) -> Void)
}
protocol DatabaseServiceProtocol {
func save(data: Data) throws
}
class DataManager {
private let networkService: NetworkServiceProtocol
private let databaseService: DatabaseServiceProtocol
// Constructor Injection, сука! Всё засовывается сразу, при рождении.
init(networkService: NetworkServiceProtocol,
databaseService: DatabaseServiceProtocol) {
self.networkService = networkService
self.databaseService = databaseService
}
func fetchAndSave() {
networkService.fetchData { [weak self] result in
// ... тут логика, не суть важно
}
}
}
Видишь? DataManager нихуя не знает, какой там конкретно NetworkService прилетит — настоящий с запросами или муляж для тестов. Он просто требует: «Дайте мне что-то, что умеет fetchData, а остальное — похуй». Красота, блядь!
Второй способ — Property Injection. Это когда зависимость можно воткнуть уже после создания объекта, через свойство. Типа, «ой, забыл, держи сейчас». Используется, когда сервис опциональный, необязательный.
class AnalyticsManager {
var analyticsService: AnalyticsServiceProtocol? // Смотри, опционал!
func trackEvent(_ event: String) {
analyticsService?.track(event: event) // А есть ли он вообще? Хуй его знает!
}
}
Третий — Method Injection. Это когда ты передаёшь зависимость прямо в метод, как аргумент. Как одноразовый стаканчик, использовал и выкинул.
class ImageProcessor {
func process(image: UIImage, using filter: FilterProtocol) -> UIImage {
return filter.apply(to: image) // На, обработай этим фильтром и отъебись!
}
}
А теперь, блядь, самое интересное — контейнеры. Это такие банки, где все твои сервисы живут, как солёные огурцы. Ты их там регистрируешь, а потом просто достаёшь, когда надо. Руками это делать, конечно, можно, но это как гвозди микроскопом забивать.
class DIContainer {
private var services: [String: Any] = [:] // Вот тут вся наша братва сидит
func register<Service>(_ type: Service.Type, factory: @escaping () -> Service) {
let key = String(describing: type)
services[key] = factory // Записали рецепт, как создать сервис
}
func resolve<Service>(_ type: Service.Type) -> Service? {
let key = String(describing: type)
guard let factory = services[key] as? () -> Service else {
return nil // Искали-искали, нихуя не нашли
}
return factory() // Ага, вот он! Родили на свет божий.
}
}
Нормальные пацаны для этого используют готовые фреймворки — Swinject, Needle. Там уже всё придумано, тебе только протоколы описать да сказать, что с чем связано.
А зачем этот весь цирк, спросишь? Да затем, сука!
- Тестируемость — овердохуищная. Хочешь протестировать
DataManager? Подсунул ему вместо настоящего сетевого сервиса — муляж, который всегда говорит «всё ок» или «всё пиздец». И проверяй, как он себя поведёт. Никаких реальных запросов, чистая изоляция! - Гибкость, блядь. Захотел сменить аналитику с Firebase на Яндекс.Метрику? Поменял одну строчку регистрации в контейнере — и весь проект использует новую либу. Не надо по всему коду ползать.
- Архитектура чистая. Классы перестают быть всемогущими божествами, которые сами себе всё создают. Они становятся скромными работягами, которые просто требуют инструменты для работы. Это и есть тот самый принцип инверсии зависимостей — «D» из SOLID, если ты вдруг не в курсе этой ебалы.
Практические советы, чтоб не выстрелить себе в ногу:
- Протоколы — твои лучшие друзья. Без них DI превращается в хуйню. Все зависимости описывай протоколами.
- Constructor Injection — царь и бог. Используй его везде, где можно. Это самый явный и честный способ.
- Не выдумывай велосипед. На проекте больше трёх экранов — бери Swinject и не парься.
- Держись подальше от Service Locator. Это такой антипаттерн, когда у тебя есть глобальная помойка
ServiceLocator.shared.get(.network). Это скрытая глобальная зависимость, хуже сифилиса. Избегай.
Вот и вся магия, ёпта. Вроде сложно, но как вникнешь — жить без этого не сможешь. Код становится прозрачным, как слёза младенца, и тестируемым, как лабораторная крыса. Главное — начать.