Приведи практические примеры применения принципов SOLID в Swift.

«Приведи практические примеры применения принципов SOLID в Swift.» — вопрос из категории Архитектура, который задают на 10% собеседований IOS Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

SOLID — это набор из пяти принципов объектно-ориентированного дизайна, направленных на создание поддерживаемого и расширяемого кода.

1. S (Single Responsibility Principle — Принцип единственной ответственности)

Класс должен иметь одну и только одну причину для изменения.

// НАРУШЕНИЕ: Класс занимается и логикой, и сохранением, и валидацией.
class UserManager {
    func authenticate(user: String, pass: String) { /* ... */ }
    func saveToDatabase(user: User) { /* ... */ }
    func validateEmail(_ email: String) -> Bool { /* ... */ }
}

// СОБЛЮДЕНИЕ: Ответственности разделены.
class UserAuthenticator {
    func authenticate(credentials: Credentials) -> Bool { /* ... */ }
}

class UserRepository {
    func save(user: User) throws { /* ... */ }
    func fetch(userId: String) -> User? { /* ... */ }
}

class EmailValidator {
    func validate(_ email: String) -> Bool { /* ... */ }
}

2. O (Open/Closed Principle — Принцип открытости/закрытости)

Классы должны быть открыты для расширения, но закрыты для модификации.

protocol ReportGenerator {
    func generateReport() -> String
}

class PDFReportGenerator: ReportGenerator {
    func generateReport() -> String { return "PDF Report" }
}

class HTMLReportGenerator: ReportGenerator {
    func generateReport() -> String { return "HTML Report" }
}

class ReportProcessor {
    // Класс закрыт для модификации (не меняем его код),
    // но открыт для расширения (можно добавить новый тип генератора).
    func processReport(using generator: ReportGenerator) {
        let report = generator.generateReport()
        print("Processing: (report)")
    }
}

3. L (Liskov Substitution Principle — Принцип подстановки Барбары Лисков)

Объекты базового класса должны быть заменяемы объектами подклассов без изменения корректности программы.

// Базовый класс и подклассы, которые его корректно заменяют.
protocol Shape {
    var area: Double { get }
}

struct Rectangle: Shape {
    let width, height: Double
    var area: Double { width * height }
}

struct Circle: Shape {
    let radius: Double
    var area: Double { .pi * radius * radius }
}

// Функция работает с любым Shape, соблюдая LSP.
func printArea(of shape: Shape) {
    print("Area is: (shape.area)")
}

4. I (Interface Segregation Principle — Принцип разделения интерфейса)

Клиенты не должны зависеть от методов, которые они не используют.

// НАРУШЕНИЕ: Многофункциональный протокол заставляет SimplePrinter реализовывать ненужный метод.
protocol MultiFunctionDevice {
    func print(document: String)
    func scan() -> Data
    func fax(document: String)
}

// СОБЛЮДЕНИЕ: Протоколы разделены по функциям.
protocol Printable {
    func print(document: String)
}

protocol Scannable {
    func scan() -> Data
}

class SimplePrinter: Printable { // Реализует только нужное
    func print(document: String) { /* ... */ }
}

class Photocopier: Printable, Scannable { // Реализует несколько ролей
    func print(document: String) { /* ... */ }
    func scan() -> Data { /* ... */ }
}

5. D (Dependency Inversion Principle — Принцип инверсии зависимостей)

Модули верхнего уровня не должны зависеть от модулей нижнего уровня. Оба должны зависеть от абстракций.

// Абстракция (протокол)
protocol DataStorage {
    func save(data: Data, forKey key: String)
    func loadData(forKey key: String) -> Data?
}

// Низкоуровневый модуль (деталь реализации)
class UserDefaultsStorage: DataStorage {
    func save(data: Data, forKey key: String) { UserDefaults.standard.set(data, forKey: key) }
    func loadData(forKey key: String) -> Data? { UserDefaults.standard.data(forKey: key) }
}

class KeychainStorage: DataStorage { /* ... */ }

// Высокоуровневый модуль зависит от абстракции, а не от конкретного хранилища.
class SettingsManager {
    private let storage: DataStorage // Зависимость от протокола

    // Внедрение зависимости через инициализатор (Dependency Injection)
    init(storage: DataStorage = UserDefaultsStorage()) {
        self.storage = storage
    }

    func saveSettings(_ settings: Settings) {
        let data = try! JSONEncoder().encode(settings)
        storage.save(data: data, forKey: "userSettings")
    }
}