Что такое принципы SOLID в объектно-ориентированном программировании?

«Что такое принципы SOLID в объектно-ориентированном программировании?» — вопрос из категории ООП, который задают на 35% собеседований IOS Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

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

  1. Принцип единственной ответственности (Single Responsibility Principle, SRP) Класс должен иметь одну и только одну причину для изменения (т.е., одну ответственность).

    // Плохо: Класс занимается и загрузкой, и парсингом.
    // Хорошо: Разделение ответственностей.
    class DataLoader {
        func load(from url: URL) -> Data? { /* ... */ }
    }
    class DataParser {
        func parse(_ data: Data) -> Model? { /* ... */ }
    }
  2. Принцип открытости/закрытости (Open/Closed Principle, OCP) Классы должны быть открыты для расширения (через наследование, композицию, протоколы), но закрыты для модификации.

    protocol PaymentMethod {
        func processPayment(amount: Double)
    }
    class CreditCardPayment: PaymentMethod { /* ... */ }
    class ApplePayPayment: PaymentMethod { /* ... */ }
    // Система платежей расширяется новыми классами без изменения существующего кода.
  3. Принцип подстановки Барбары Лисков (Liskov Substitution Principle, LSP) Объекты подклассов должны быть способны заменять объекты своих суперклассов, не нарушая работу программы.

  4. Принцип разделения интерфейса (Interface Segregation Principle, ISP) Много специализированных интерфейсов (протоколов) лучше, чем один универсальный. Клиенты не должны зависеть от методов, которые они не используют.

    // Вместо одного "толстого" протокола:
    // protocol Worker { func work(); func eat(); }
    // Используем два узких:
    protocol Workable { func work() }
    protocol Eatable { func eat() }
  5. Принцип инверсии зависимостей (Dependency Inversion Principle, DIP) Модули верхнего уровня не должны зависеть от модулей нижнего уровня. Оба должны зависеть от абстракций. Абстракции не должны зависеть от деталей, детали должны зависеть от абстракций.

    protocol StorageService {
        func save(data: Data)
    }
    class CoreDataService: StorageService { /* ... */ }
    class RealmService: StorageService { /* ... */ }
    class DataManager {
        private let storage: StorageService // Зависимость от абстракции
        init(storage: StorageService) {
            self.storage = storage
        }
    }