Что такое принципы SOLID и как вы их применяете?

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

Ответ

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

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

// ПЛОХО: Класс занимается и логикой, и сохранением
class User {
    func validate() { ... }
    func saveToDatabase() { ... }
}

// ХОРОШО: Разделение ответственности
class UserValidator {
    func validate(_ user: User) -> Bool { ... }
}

class UserRepository {
    func save(_ user: User) { ... }
}

2. Open-Closed (Принцип открытости/закрытости) Классы должны быть открыты для расширения, но закрыты для модификации.

protocol PaymentMethod {
    func processPayment(amount: Double)
}

class CreditCardPayment: PaymentMethod {
    func processPayment(amount: Double) { ... }
}

class PayPalPayment: PaymentMethod {
    func processPayment(amount: Double) { ... }
}

class PaymentProcessor {
    func process(payment: PaymentMethod, amount: Double) {
        payment.processPayment(amount: amount)
    }
}

3. Liskov Substitution (Принцип подстановки Барбары Лисков) Объекты должны быть заменяемыми экземплярами своих базовых классов без изменения корректности программы.

// Нарушение принципа:
class Bird {
    func fly() { ... }
}

class Penguin: Bird { // Пингвин не может летать!
    override func fly() {
        fatalError("Penguins can't fly")
    }
}

// Решение: Разделить интерфейсы
protocol Bird {
    func eat()
}

protocol FlyingBird: Bird {
    func fly()
}

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

// ПЛОХО: Один громоздкий протокол
protocol Worker {
    func work()
    func eat()
    func sleep()
}

// ХОРОШО: Разделенные протоколы
protocol Workable {
    func work()
}

protocol Eatable {
    func eat()
}

class Robot: Workable { // Роботу не нужно есть
    func work() { ... }
}

5. Dependency Inversion (Принцип инверсии зависимостей) Зависимости должны строиться на абстракциях, а не на конкретных реализациях.

// ПЛОХО: Зависимость от конкретного класса
class DataService {
    private let database = MySQLDatabase() // Жесткая привязка
}

// ХОРОШО: Зависимость от абстракции
protocol Database {
    func save(data: Any)
}

class DataService {
    private let database: Database

    init(database: Database) { // Внедрение зависимости
        self.database = database
    }
}

Практическое применение:

  • Использую протоколы для абстракции зависимостей
  • Разделяю большие классы на мелкие с одной ответственностью
  • Применяю Dependency Injection для тестируемости
  • Следую принципам осознанно, но без фанатизма — иногда прагматизм важнее строгого следования догмам