Какие принципы SOLID применяются в разработке?

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

Ответ

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

  1. S — Single Responsibility (Принцип единственной ответственности) Класс должен иметь одну и только одну причину для изменения. Он должен решать строго одну задачу.

    • Плохо: Класс DataManager, который и загружает данные из сети, и сохраняет их в БД, и парсит JSON.
    • Хорошо: Разделить на NetworkService, DatabaseService, Parser.
  2. O — Open/Closed (Принцип открытости/закрытости) Классы должны быть открыты для расширения, но закрыты для модификации. Новый функционал добавляется через новые сущности, а не изменением существующего кода.

    protocol Shape {
        func area() -> Double
    }
    class Rectangle: Shape { ... }
    class Circle: Shape { ... }
    // Чтобы добавить новую фигуру, мы создаем новый класс, а не меняем существующую логику вычисления площади.
  3. L — Liskov Substitution (Принцип подстановки Барбары Лисков) Объекты в программе должны быть заменяемыми на экземпляры их подтипов без изменения правильности программы. Наследник не должен ужесточать предусловия или ослаблять постусловия базового класса.

    • Нарушение: Класс Square, наследующий от Rectangle. Изменение ширины у Square неявно меняет и высоту, что ломает логику клиентского кода, ожидающего независимого управления сторонами.
  4. I — Interface Segregation (Принцип разделения интерфейса) Много специализированных интерфейсов лучше, чем один универсальный. Клиент не должен зависеть от методов, которые он не использует.

    • Плохо: Монолитный протокол Worker с методами work(), eat(), sleep().
    • Хорошо: Разделить на Workable, Eatable, Sleepable. Робот реализует только Workable.
  5. D — Dependency Inversion (Принцип инверсии зависимостей) Модули верхнего уровня не должны зависеть от модулей нижнего уровня. Оба должны зависеть от абстракций. Абстракции не должны зависеть от деталей. Детали должны зависеть от абстракций.

    // Зависимость от абстракции (протокола), а не от конкретной реализации.
    protocol Storage {
        func save(data: Data)
    }
    class ViewModel {
        let storage: Storage // Не `CoreDataStorage` или `RealmStorage`
        init(storage: Storage) { self.storage = storage }
    }