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

Ответ

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
        }
    }

Ответ 18+ 🔞

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

Принцип единственной ответственности (SRP)

Это ж элементарно, Ватсон! Один класс — одна работа. Не надо делать из него швейцарский нож, который и данные грузит, и парсит, и в базу пихает, и тебе кофе варит. Представь себе чувака на кухне: один режет лук, другой жарит, третий моет посуду. А если один мудила будет всё делать — он либо лук пережжёт, либо посуду разобьёт, либо себе палец отрежет. Вот и тут так же.

// Плохо: Это же пиздец, а не класс. Он и швец, и жнец, и на дуде игрец.
// Хорошо: Разделили, как взрослые люди.
class DataLoader {
    func load(from url: URL) -> Data? { /* ... */ } // Только грузит, блядь
}
class DataParser {
    func parse(_ data: Data) -> Model? { /* ... */ } // Только парсит, ёпта
}

Принцип открытости/закрытости (OCP)

Суть в том, что твой код должен быть как дом с кучей розеток. Хочешь новый телевизор — воткнул в розетку и работает. Не надо долбить стены и перекладывать провода каждый раз. То есть система должна расширяться новыми фичами (открыта), но при этом не требовать переписывания старого, работающего кода (закрыта).

protocol PaymentMethod {
    func processPayment(amount: Double)
}
class CreditCardPayment: PaymentMethod { /* ... */ } // Воткнули одну вилку
class ApplePayPayment: PaymentMethod { /* ... */ } // Воткнули другую
class CryptoPayment: PaymentMethod { /* ... */ } // Захотелось крипты — написали новый класс и ВСЁ. Старый код даже не чихнул.

Принцип подстановки Барбары Лисков (LSP)

Тут история про доверие, ёбана. Если у тебя есть класс Птица с методом летать(), и от него наследуется класс Пингвин, то у тебя, дружок, проблемы. Потому что пингвин — птица, но летать него не получается, и если ты его подсунешь вместо любой другой птицы в код, который ожидает полёт, будет облом. Наследник должен быть полноценной заменой родителя, а не каким-то полупидором с ограниченными возможностями.

Принцип разделения интерфейса (ISP)

Это про то, что не надо всем подряд навязывать то, что им не нужно. Представь, что ты заходишь в кафе, а тебе дают меню на 50 страниц, где есть и суши, и борщ, и шаурма. Ты хочешь только кофе. Зачем тебе этот кирпич? Вот и интерфейсы (протоколы) должны быть узкими и конкретными.

// Плохо: Заставляем робота-сварщика есть. Он что, батарейки жрать должен?
// protocol Worker { func work(); func eat(); }
// Хорошо: Сделали отдельные меню.
protocol Workable { func work() } // Меню для работы
protocol Eatable { func eat() }   // Меню для еды
class Robot: Workable { /* ... */ } // Робот берёт только Workable и спокоен.
class Human: Workable, Eatable { /* ... */ } // Человек берёт оба.

Принцип инверсии зависимостей (DIP)

Это, блядь, самый важный, чтобы не попасть в просак. Высокоуровневые модули (логика приложения) не должны зависеть от низкоуровневых (работа с базой, сетью). Они должны зависеть от абстракций (протоколов). Это как если бы твой начальник (логика) зависел не от тебя лично (конкретный класс Вася), а от должности Исполнитель. Уволили тебя — наняли другого на ту же должность, начальнику похуй, работа идёт.

protocol StorageService { // Абстракция. Должность "Хранитель данных".
    func save(data: Data)
}
class CoreDataService: StorageService { /* ... */ } // Конкретный Вася
class RealmService: StorageService { /* ... */ }    // Конкретный Петя
class FileSystemService: StorageService { /* ... */ } // Конкретный Гена

class DataManager { // Начальник (высокоуровневый модуль)
    private let storage: StorageService // Он зависит от ДОЛЖНОСТИ, а не от Васи!
    init(storage: StorageService) { // Даём ему любого исполнителя на эту должность
        self.storage = storage
    }
    // Меняй CoreData на Realm хоть на ходу — DataManager даже не почует подвоха.
}

Вот и вся магия, ёпта. Соблюдаешь эти правила — код живёт долго и счастливо, не соблюдаешь — получаешь монолитного Франкенштейна, которого все боятся и который развалится от чиха.