Ответ
Наиболее сложным для последовательного соблюдения является Принцип инверсии зависимостей (Dependency Inversion Principle - DIP).
Формулировка: Модули верхнего уровня не должны зависеть от модулей нижнего уровня. Оба должны зависеть от абстракций. Абстракции не должны зависеть от деталей. Детали должны зависеть от абстракций.
Почему это сложно?
- Привычка к прямым зависимостям: В iOS SDK многие компоненты (
URLSession,UserDefaults.standard,FileManager.default) представлены как синглтоны или конкретные классы, что провоцирует прямое использование. - Дополнительная абстракция: Требует создания протоколов для каждого сервиса, что кажется избыточным в небольших задачах.
- Сложность внедрения: Реализация Dependency Injection (через инициализатор или property) увеличивает объем boilerplate-кода.
- Давление сроков: Под дедлайнами проще и быстрее нарушить принцип, чем правильно абстрагировать зависимости.
Пример нарушения DIP:
class DataManager {
func fetchData() {
// Прямая, жесткая зависимость от конкретной реализации
URLSession.shared.dataTask(with: url) { ... }
}
}
Пример соблюдения DIP:
// 1. Абстракция (протокол)
protocol NetworkService {
func fetchData(from url: URL, completion: @escaping (Result<Data, Error>) -> Void)
}
// 2. Деталь (конкретная реализация)
class DefaultNetworkService: NetworkService {
func fetchData(from url: URL, completion: @escaping (Result<Data, Error>) -> Void) {
URLSession.shared.dataTask(with: url) { data, _, error in
// ... обработка ответа
}.resume()
}
}
// 3. Модуль верхнего уровня зависит от абстракции
class DataManager {
private let networkService: NetworkService
// Внедрение зависимости через инициализатор
init(networkService: NetworkService) {
self.networkService = networkService
}
func loadData() {
networkService.fetchData(from: someURL) { result in
// ...
}
}
}
// Это делает код тестируемым (можно подменить NetworkService моком).